app_voicemail.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 1999 - 2006, Digium, Inc.
00005  *
00006  * Mark Spencer <markster@digium.com>
00007  *
00008  * See http://www.asterisk.org for more information about
00009  * the Asterisk project. Please do not directly contact
00010  * any of the maintainers of this project for assistance;
00011  * the project provides a web site, mailing lists and IRC
00012  * channels for your use.
00013  *
00014  * This program is free software, distributed under the terms of
00015  * the GNU General Public License Version 2. See the LICENSE file
00016  * at the top of the source tree.
00017  */
00018 
00019 /*!
00020  * \file
00021  * \author Mark Spencer <markster@digium.com>
00022  * \brief Comedian Mail - Voicemail System
00023  *
00024  * unixODBC (http://www.unixodbc.org/)
00025  * A source distribution of University of Washington's IMAP c-client
00026  *         (http://www.washington.edu/imap/)
00027  *
00028  * \par See also
00029  * \arg \ref Config_vm
00030  * \note For information about voicemail IMAP storage, https://wiki.asterisk.org/wiki/display/AST/IMAP+Voicemail+Storage
00031  * \ingroup applications
00032  * \todo This module requires res_adsi to load. This needs to be optional
00033  * during compilation.
00034  *
00035  * \todo This file is now almost impossible to work with, due to all \#ifdefs.
00036  *       Feels like the database code before realtime. Someone - please come up
00037  *       with a plan to clean this up.
00038  */
00039 
00040 /*! \li \ref app_voicemail.c uses configuration file \ref voicemail.conf
00041  * \addtogroup configuration_file Configuration Files
00042  */
00043 
00044 /*! 
00045  * \page voicemail.conf voicemail.conf
00046  * \verbinclude voicemail.conf.sample
00047  */
00048 
00049 /*** MODULEINFO
00050    <defaultenabled>yes</defaultenabled>
00051    <conflict>res_mwi_external</conflict>
00052    <use type="module">res_adsi</use>
00053    <use type="module">res_smdi</use>
00054    <support_level>core</support_level>
00055  ***/
00056 
00057 /*** MAKEOPTS
00058 <category name="MENUSELECT_OPTS_app_voicemail" displayname="Voicemail Build Options" positive_output="yes" touch_on_change="apps/app_voicemail.c apps/app_directory.c">
00059    <member name="FILE_STORAGE" displayname="Storage of Voicemail using filesystem">
00060       <conflict>ODBC_STORAGE</conflict>
00061       <conflict>IMAP_STORAGE</conflict>
00062       <defaultenabled>yes</defaultenabled>
00063       <support_level>core</support_level>
00064    </member>
00065    <member name="ODBC_STORAGE" displayname="Storage of Voicemail using ODBC">
00066       <depend>generic_odbc</depend>
00067       <depend>ltdl</depend>
00068       <conflict>IMAP_STORAGE</conflict>
00069       <conflict>FILE_STORAGE</conflict>
00070       <defaultenabled>no</defaultenabled>
00071       <support_level>core</support_level>
00072    </member>
00073    <member name="IMAP_STORAGE" displayname="Storage of Voicemail using IMAP4">
00074       <depend>imap_tk</depend>
00075       <conflict>ODBC_STORAGE</conflict>
00076       <conflict>FILE_STORAGE</conflict>
00077       <use type="external">openssl</use>
00078       <defaultenabled>no</defaultenabled>
00079       <support_level>core</support_level>
00080    </member>
00081 </category>
00082 ***/
00083 
00084 #include "asterisk.h"
00085 
00086 #ifdef IMAP_STORAGE
00087 #include <ctype.h>
00088 #include <signal.h>
00089 #include <pwd.h>
00090 #ifdef USE_SYSTEM_IMAP
00091 #include <imap/c-client.h>
00092 #include <imap/imap4r1.h>
00093 #include <imap/linkage.h>
00094 #elif defined (USE_SYSTEM_CCLIENT)
00095 #include <c-client/c-client.h>
00096 #include <c-client/imap4r1.h>
00097 #include <c-client/linkage.h>
00098 #else
00099 #include "c-client.h"
00100 #include "imap4r1.h"
00101 #include "linkage.h"
00102 #endif
00103 #endif
00104 
00105 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 433689 $")
00106 
00107 #include "asterisk/paths.h"   /* use ast_config_AST_SPOOL_DIR */
00108 #include <sys/time.h>
00109 #include <sys/stat.h>
00110 #include <sys/mman.h>
00111 #include <time.h>
00112 #include <dirent.h>
00113 #if defined(__FreeBSD__) || defined(__OpenBSD__)
00114 #include <sys/wait.h>
00115 #endif
00116 
00117 #include "asterisk/logger.h"
00118 #include "asterisk/lock.h"
00119 #include "asterisk/file.h"
00120 #include "asterisk/channel.h"
00121 #include "asterisk/pbx.h"
00122 #include "asterisk/config.h"
00123 #include "asterisk/say.h"
00124 #include "asterisk/module.h"
00125 #include "asterisk/adsi.h"
00126 #include "asterisk/app.h"
00127 #include "asterisk/manager.h"
00128 #include "asterisk/dsp.h"
00129 #include "asterisk/localtime.h"
00130 #include "asterisk/cli.h"
00131 #include "asterisk/utils.h"
00132 #include "asterisk/stringfields.h"
00133 #include "asterisk/strings.h"
00134 #include "asterisk/smdi.h"
00135 #include "asterisk/astobj2.h"
00136 #include "asterisk/taskprocessor.h"
00137 #include "asterisk/test.h"
00138 #include "asterisk/format_cache.h"
00139 
00140 #ifdef ODBC_STORAGE
00141 #include "asterisk/res_odbc.h"
00142 #endif
00143 
00144 #ifdef IMAP_STORAGE
00145 #include "asterisk/threadstorage.h"
00146 #endif
00147 
00148 /*** DOCUMENTATION
00149    <application name="VoiceMail" language="en_US">
00150       <synopsis>
00151          Leave a Voicemail message.
00152       </synopsis>
00153       <syntax>
00154          <parameter name="mailboxs" argsep="&amp;" required="true">
00155             <argument name="mailbox1" argsep="@" required="true">
00156                <argument name="mailbox" required="true" />
00157                <argument name="context" />
00158             </argument>
00159             <argument name="mailbox2" argsep="@" multiple="true">
00160                <argument name="mailbox" required="true" />
00161                <argument name="context" />
00162             </argument>
00163          </parameter>
00164          <parameter name="options">
00165             <optionlist>
00166                <option name="b">
00167                   <para>Play the <literal>busy</literal> greeting to the calling party.</para>
00168                </option>
00169                <option name="d">
00170                   <argument name="c" />
00171                   <para>Accept digits for a new extension in context <replaceable>c</replaceable>,
00172                   if played during the greeting. Context defaults to the current context.</para>
00173                </option>
00174                <option name="g">
00175                   <argument name="#" required="true" />
00176                   <para>Use the specified amount of gain when recording the voicemail
00177                   message. The units are whole-number decibels (dB). Only works on supported
00178                   technologies, which is DAHDI only.</para>
00179                </option>
00180                <option name="s">
00181                   <para>Skip the playback of instructions for leaving a message to the
00182                   calling party.</para>
00183                </option>
00184                <option name="u">
00185                   <para>Play the <literal>unavailable</literal> greeting.</para>
00186                </option>
00187                <option name="U">
00188                   <para>Mark message as <literal>URGENT</literal>.</para>
00189                </option>
00190                <option name="P">
00191                   <para>Mark message as <literal>PRIORITY</literal>.</para>
00192                </option>
00193             </optionlist>
00194          </parameter>
00195       </syntax>
00196       <description>
00197          <para>This application allows the calling party to leave a message for the specified
00198          list of mailboxes. When multiple mailboxes are specified, the greeting will be taken from
00199          the first mailbox specified. Dialplan execution will stop if the specified mailbox does not
00200          exist.</para>
00201          <para>The Voicemail application will exit if any of the following DTMF digits are received:</para>
00202          <enumlist>
00203             <enum name="0">
00204                <para>Jump to the <literal>o</literal> extension in the current dialplan context.</para>
00205             </enum>
00206             <enum name="*">
00207                <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
00208             </enum>
00209          </enumlist>
00210          <para>This application will set the following channel variable upon completion:</para>
00211          <variablelist>
00212             <variable name="VMSTATUS">
00213                <para>This indicates the status of the execution of the VoiceMail application.</para>
00214                <value name="SUCCESS" />
00215                <value name="USEREXIT" />
00216                <value name="FAILED" />
00217             </variable>
00218          </variablelist>
00219       </description>
00220       <see-also>
00221          <ref type="application">VoiceMailMain</ref>
00222       </see-also>
00223    </application>
00224    <application name="VoiceMailMain" language="en_US">
00225       <synopsis>
00226          Check Voicemail messages.
00227       </synopsis>
00228       <syntax>
00229          <parameter name="mailbox" required="true" argsep="@">
00230             <argument name="mailbox" />
00231             <argument name="context" />
00232          </parameter>
00233          <parameter name="options">
00234             <optionlist>
00235                <option name="p">
00236                   <para>Consider the <replaceable>mailbox</replaceable> parameter as a prefix to
00237                   the mailbox that is entered by the caller.</para>
00238                </option>
00239                <option name="g">
00240                   <argument name="#" required="true" />
00241                   <para>Use the specified amount of gain when recording a voicemail message.
00242                   The units are whole-number decibels (dB).</para>
00243                </option>
00244                <option name="s">
00245                   <para>Skip checking the passcode for the mailbox.</para>
00246                </option>
00247                <option name="a">
00248                   <argument name="folder" required="true" />
00249                   <para>Skip folder prompt and go directly to <replaceable>folder</replaceable> specified.
00250                   Defaults to <literal>INBOX</literal> (or <literal>0</literal>).</para>
00251                   <enumlist>
00252                      <enum name="0"><para>INBOX</para></enum>
00253                      <enum name="1"><para>Old</para></enum>
00254                      <enum name="2"><para>Work</para></enum>
00255                      <enum name="3"><para>Family</para></enum>
00256                      <enum name="4"><para>Friends</para></enum>
00257                      <enum name="5"><para>Cust1</para></enum>
00258                      <enum name="6"><para>Cust2</para></enum>
00259                      <enum name="7"><para>Cust3</para></enum>
00260                      <enum name="8"><para>Cust4</para></enum>
00261                      <enum name="9"><para>Cust5</para></enum>
00262                   </enumlist>
00263                </option>
00264             </optionlist>
00265          </parameter>
00266       </syntax>
00267       <description>
00268          <para>This application allows the calling party to check voicemail messages. A specific
00269          <replaceable>mailbox</replaceable>, and optional corresponding <replaceable>context</replaceable>,
00270          may be specified. If a <replaceable>mailbox</replaceable> is not provided, the calling party will
00271          be prompted to enter one. If a <replaceable>context</replaceable> is not specified, the
00272          <literal>default</literal> context will be used.</para>
00273          <para>The VoiceMailMain application will exit if the following DTMF digit is entered as Mailbox
00274          or Password, and the extension exists:</para>
00275          <enumlist>
00276             <enum name="*">
00277                <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
00278             </enum>
00279          </enumlist>
00280       </description>
00281       <see-also>
00282          <ref type="application">VoiceMail</ref>
00283       </see-also>
00284    </application>
00285    <application name="MailboxExists" language="en_US">
00286       <synopsis>
00287          Check to see if Voicemail mailbox exists.
00288       </synopsis>
00289       <syntax>
00290          <parameter name="mailbox" required="true" argsep="@">
00291             <argument name="mailbox" required="true" />
00292             <argument name="context" />
00293          </parameter>
00294          <parameter name="options">
00295             <para>None options.</para>
00296          </parameter>
00297       </syntax>
00298       <description>
00299          <note><para>DEPRECATED. Use VM_INFO(mailbox[@context],exists) instead.</para></note>
00300          <para>Check to see if the specified <replaceable>mailbox</replaceable> exists. If no voicemail
00301          <replaceable>context</replaceable> is specified, the <literal>default</literal> context
00302          will be used.</para>
00303          <para>This application will set the following channel variable upon completion:</para>
00304          <variablelist>
00305             <variable name="VMBOXEXISTSSTATUS">
00306                <para>This will contain the status of the execution of the MailboxExists application.
00307                Possible values include:</para>
00308                <value name="SUCCESS" />
00309                <value name="FAILED" />
00310             </variable>
00311          </variablelist>
00312       </description>
00313       <see-also>
00314          <ref type="function">VM_INFO</ref>
00315       </see-also>
00316    </application>
00317    <application name="VMAuthenticate" language="en_US">
00318       <synopsis>
00319          Authenticate with Voicemail passwords.
00320       </synopsis>
00321       <syntax>
00322          <parameter name="mailbox" required="true" argsep="@">
00323             <argument name="mailbox" />
00324             <argument name="context" />
00325          </parameter>
00326          <parameter name="options">
00327             <optionlist>
00328                <option name="s">
00329                   <para>Skip playing the initial prompts.</para>
00330                </option>
00331             </optionlist>
00332          </parameter>
00333       </syntax>
00334       <description>
00335          <para>This application behaves the same way as the Authenticate application, but the passwords
00336          are taken from <filename>voicemail.conf</filename>. If the <replaceable>mailbox</replaceable> is
00337          specified, only that mailbox's password will be considered valid. If the <replaceable>mailbox</replaceable>
00338          is not specified, the channel variable <variable>AUTH_MAILBOX</variable> will be set with the authenticated
00339          mailbox.</para>
00340          <para>The VMAuthenticate application will exit if the following DTMF digit is entered as Mailbox
00341          or Password, and the extension exists:</para>
00342          <enumlist>
00343             <enum name="*">
00344                <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
00345             </enum>
00346          </enumlist>
00347       </description>
00348    </application>
00349    <application name="VoiceMailPlayMsg" language="en_US">
00350       <synopsis>
00351          Play a single voice mail msg from a mailbox by msg id.
00352       </synopsis>
00353       <syntax>
00354          <parameter name="mailbox" required="true" argsep="@">
00355             <argument name="mailbox" />
00356             <argument name="context" />
00357          </parameter>
00358          <parameter name="msg_id" required="true">
00359             <para>The msg id of the msg to play back. </para>
00360          </parameter>
00361       </syntax>
00362       <description>
00363          <para>This application sets the following channel variable upon completion:</para>
00364          <variablelist>
00365             <variable name="VOICEMAIL_PLAYBACKSTATUS">
00366                <para>The status of the playback attempt as a text string.</para>
00367                <value name="SUCCESS"/>
00368                <value name="FAILED"/>
00369             </variable>
00370          </variablelist>
00371       </description>
00372    </application>
00373    <application name="VMSayName" language="en_US">
00374       <synopsis>
00375          Play the name of a voicemail user
00376       </synopsis>
00377       <syntax>
00378          <parameter name="mailbox" required="true" argsep="@">
00379             <argument name="mailbox" />
00380             <argument name="context" />
00381          </parameter>
00382       </syntax>
00383       <description>
00384          <para>This application will say the recorded name of the voicemail user specified as the
00385          argument to this application. If no context is provided, <literal>default</literal> is assumed.</para>
00386       </description>
00387    </application>
00388    <function name="MAILBOX_EXISTS" language="en_US">
00389       <synopsis>
00390          Tell if a mailbox is configured.
00391       </synopsis>
00392       <syntax argsep="@">
00393          <parameter name="mailbox" required="true" />
00394          <parameter name="context" />
00395       </syntax>
00396       <description>
00397          <note><para>DEPRECATED. Use VM_INFO(mailbox[@context],exists) instead.</para></note>
00398          <para>Returns a boolean of whether the corresponding <replaceable>mailbox</replaceable> exists.
00399          If <replaceable>context</replaceable> is not specified, defaults to the <literal>default</literal>
00400          context.</para>
00401       </description>
00402       <see-also>
00403          <ref type="function">VM_INFO</ref>
00404       </see-also>
00405    </function>
00406    <function name="VM_INFO" language="en_US">
00407       <synopsis>
00408          Returns the selected attribute from a mailbox.
00409       </synopsis>
00410       <syntax argsep=",">
00411          <parameter name="mailbox" argsep="@" required="true">
00412             <argument name="mailbox" required="true" />
00413             <argument name="context" />
00414          </parameter>
00415          <parameter name="attribute" required="true">
00416             <optionlist>
00417                <option name="count">
00418                   <para>Count of messages in specified <replaceable>folder</replaceable>.
00419                   If <replaceable>folder</replaceable> is not specified, defaults to <literal>INBOX</literal>.</para>
00420                </option>
00421                <option name="email">
00422                   <para>E-mail address associated with the mailbox.</para>
00423                </option>
00424                <option name="exists">
00425                   <para>Returns a boolean of whether the corresponding <replaceable>mailbox</replaceable> exists.</para>
00426                </option>
00427                <option name="fullname">
00428                   <para>Full name associated with the mailbox.</para>
00429                </option>
00430                <option name="language">
00431                   <para>Mailbox language if overridden, otherwise the language of the channel.</para>
00432                </option>
00433                <option name="locale">
00434                   <para>Mailbox locale if overridden, otherwise global locale.</para>
00435                </option>
00436                <option name="pager">
00437                   <para>Pager e-mail address associated with the mailbox.</para>
00438                </option>
00439                <option name="password">
00440                   <para>Mailbox access password.</para>
00441                </option>
00442                <option name="tz">
00443                   <para>Mailbox timezone if overridden, otherwise global timezone</para>
00444                </option>
00445             </optionlist>
00446          </parameter>
00447          <parameter name="folder" required="false">
00448             <para>If not specified, <literal>INBOX</literal> is assumed.</para>
00449          </parameter>
00450       </syntax>
00451       <description>
00452          <para>Returns the selected attribute from the specified <replaceable>mailbox</replaceable>.
00453          If <replaceable>context</replaceable> is not specified, defaults to the <literal>default</literal>
00454          context. Where the <replaceable>folder</replaceable> can be specified, common folders
00455          include <literal>INBOX</literal>, <literal>Old</literal>, <literal>Work</literal>,
00456          <literal>Family</literal> and <literal>Friends</literal>.</para>
00457       </description>
00458    </function>
00459    <manager name="VoicemailUsersList" language="en_US">
00460       <synopsis>
00461          List All Voicemail User Information.
00462       </synopsis>
00463       <syntax>
00464          <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
00465       </syntax>
00466       <description>
00467       </description>
00468    </manager>
00469    <manager name="VoicemailRefresh" language="en_US">
00470       <synopsis>
00471          Tell Asterisk to poll mailboxes for a change
00472       </synopsis>
00473       <syntax>
00474          <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
00475          <parameter name="Context" />
00476          <parameter name="Mailbox" />
00477       </syntax>
00478       <description>
00479          <para>Normally, MWI indicators are only sent when Asterisk itself
00480          changes a mailbox.  With external programs that modify the content
00481          of a mailbox from outside the application, an option exists called
00482          <literal>pollmailboxes</literal> that will cause voicemail to
00483          continually scan all mailboxes on a system for changes.  This can
00484          cause a large amount of load on a system.  This command allows
00485          external applications to signal when a particular mailbox has
00486          changed, thus permitting external applications to modify mailboxes
00487          and MWI to work without introducing considerable CPU load.</para>
00488          <para>If <replaceable>Context</replaceable> is not specified, all
00489          mailboxes on the system will be polled for changes.  If
00490          <replaceable>Context</replaceable> is specified, but
00491          <replaceable>Mailbox</replaceable> is omitted, then all mailboxes
00492          within <replaceable>Context</replaceable> will be polled.
00493          Otherwise, only a single mailbox will be polled for changes.</para>
00494       </description>
00495    </manager>
00496  ***/
00497 
00498 #ifdef IMAP_STORAGE
00499 static char imapserver[48];
00500 static char imapport[8];
00501 static char imapflags[128];
00502 static char imapfolder[64];
00503 static char imapparentfolder[64] = "\0";
00504 static char greetingfolder[64];
00505 static char authuser[32];
00506 static char authpassword[42];
00507 static int imapversion = 1;
00508 
00509 static int expungeonhangup = 1;
00510 static int imapgreetings = 0;
00511 static char delimiter = '\0';
00512 
00513 /* mail_open cannot be protected on a stream basis */
00514 ast_mutex_t mail_open_lock;
00515 
00516 struct vm_state;
00517 struct ast_vm_user;
00518 
00519 AST_THREADSTORAGE(ts_vmstate);
00520 
00521 /* Forward declarations for IMAP */
00522 static int init_mailstream(struct vm_state *vms, int box);
00523 static void write_file(char *filename, char *buffer, unsigned long len);
00524 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len);
00525 static void vm_imap_delete(char *file, int msgnum, struct ast_vm_user *vmu);
00526 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len);
00527 static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive);
00528 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive);
00529 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu);
00530 static void vmstate_insert(struct vm_state *vms);
00531 static void vmstate_delete(struct vm_state *vms);
00532 static void set_update(MAILSTREAM * stream);
00533 static void init_vm_state(struct vm_state *vms);
00534 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro);
00535 static void get_mailbox_delimiter(struct vm_state *vms, MAILSTREAM *stream);
00536 static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota);
00537 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int target);
00538 static int imap_store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag, const char *msg_id);
00539 static void vm_imap_update_msg_id(char *dir, int msgnum, const char *msg_id, struct ast_vm_user *vmu, struct ast_config *msg_cfg, int folder);
00540 static void update_messages_by_imapuser(const char *user, unsigned long number);
00541 static int vm_delete(char *file);
00542 
00543 static int imap_remove_file (char *dir, int msgnum);
00544 static int imap_retrieve_file (const char *dir, const int msgnum, const char *mailbox, const char *context);
00545 static int imap_delete_old_greeting (char *dir, struct vm_state *vms);
00546 static void check_quota(struct vm_state *vms, char *mailbox);
00547 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
00548 struct vmstate {
00549    struct vm_state *vms;
00550    AST_LIST_ENTRY(vmstate) list;
00551 };
00552 
00553 static AST_LIST_HEAD_STATIC(vmstates, vmstate);
00554 
00555 #endif
00556 
00557 #define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
00558 
00559 #define COMMAND_TIMEOUT 5000
00560 /* Don't modify these here; set your umask at runtime instead */
00561 #define  VOICEMAIL_DIR_MODE   0777
00562 #define  VOICEMAIL_FILE_MODE  0666
00563 #define  CHUNKSIZE   65536
00564 
00565 #define VOICEMAIL_CONFIG "voicemail.conf"
00566 #define ASTERISK_USERNAME "asterisk"
00567 
00568 /* Define fast-forward, pause, restart, and reverse keys
00569  * while listening to a voicemail message - these are
00570  * strings, not characters */
00571 #define DEFAULT_LISTEN_CONTROL_FORWARD_KEY "#"
00572 #define DEFAULT_LISTEN_CONTROL_REVERSE_KEY "*"
00573 #define DEFAULT_LISTEN_CONTROL_PAUSE_KEY "0"
00574 #define DEFAULT_LISTEN_CONTROL_RESTART_KEY "2"
00575 #define DEFAULT_LISTEN_CONTROL_STOP_KEY "13456789"
00576 #define VALID_DTMF "1234567890*#" /* Yes ABCD are valid dtmf but what phones have those? */
00577 
00578 /* Default mail command to mail voicemail. Change it with the
00579  * mailcmd= command in voicemail.conf */
00580 #define SENDMAIL "/usr/sbin/sendmail -t"
00581 
00582 #define INTRO "vm-intro"
00583 
00584 #define MAXMSG 100
00585 #define MAXMSGLIMIT 9999
00586 
00587 #define MINPASSWORD 0 /*!< Default minimum mailbox password length */
00588 
00589 #define BASELINELEN 72
00590 #define BASEMAXINLINE 256
00591 #ifdef IMAP_STORAGE
00592 #define ENDL "\r\n"
00593 #else
00594 #define ENDL "\n"
00595 #endif
00596 
00597 #define MAX_DATETIME_FORMAT   512
00598 #define MAX_NUM_CID_CONTEXTS 10
00599 
00600 #define VM_REVIEW        (1 << 0)   /*!< After recording, permit the caller to review the recording before saving */
00601 #define VM_OPERATOR      (1 << 1)   /*!< Allow 0 to be pressed to go to 'o' extension */
00602 #define VM_SAYCID        (1 << 2)   /*!< Repeat the CallerID info during envelope playback */
00603 #define VM_SVMAIL        (1 << 3)   /*!< Allow the user to compose a new VM from within VoicemailMain */
00604 #define VM_ENVELOPE      (1 << 4)   /*!< Play the envelope information (who-from, time received, etc.) */
00605 #define VM_SAYDURATION   (1 << 5)   /*!< Play the length of the message during envelope playback */
00606 #define VM_SKIPAFTERCMD  (1 << 6)   /*!< After deletion, assume caller wants to go to the next message */
00607 #define VM_FORCENAME     (1 << 7)   /*!< Have new users record their name */
00608 #define VM_FORCEGREET    (1 << 8)   /*!< Have new users record their greetings */
00609 #define VM_PBXSKIP       (1 << 9)   /*!< Skip the [PBX] preamble in the Subject line of emails */
00610 #define VM_DIRECFORWARD  (1 << 10)  /*!< Permit caller to use the Directory app for selecting to which mailbox to forward a VM */
00611 #define VM_ATTACH        (1 << 11)  /*!< Attach message to voicemail notifications? */
00612 #define VM_DELETE        (1 << 12)  /*!< Delete message after sending notification */
00613 #define VM_ALLOCED       (1 << 13)  /*!< Structure was malloc'ed, instead of placed in a return (usually static) buffer */
00614 #define VM_SEARCH        (1 << 14)  /*!< Search all contexts for a matching mailbox */
00615 #define VM_TEMPGREETWARN (1 << 15)  /*!< Remind user tempgreeting is set */
00616 #define VM_MOVEHEARD     (1 << 16)  /*!< Move a "heard" message to Old after listening to it */
00617 #define VM_MESSAGEWRAP   (1 << 17)  /*!< Wrap around from the last message to the first, and vice-versa */
00618 #define VM_FWDURGAUTO    (1 << 18)  /*!< Autoset of Urgent flag on forwarded Urgent messages set globally */
00619 #define ERROR_LOCK_PATH  -100
00620 #define OPERATOR_EXIT     300
00621 
00622 enum vm_box {
00623    NEW_FOLDER,
00624    OLD_FOLDER,
00625    WORK_FOLDER,
00626    FAMILY_FOLDER,
00627    FRIENDS_FOLDER,
00628    GREETINGS_FOLDER
00629 };
00630 
00631 enum vm_option_flags {
00632    OPT_SILENT =           (1 << 0),
00633    OPT_BUSY_GREETING =    (1 << 1),
00634    OPT_UNAVAIL_GREETING = (1 << 2),
00635    OPT_RECORDGAIN =       (1 << 3),
00636    OPT_PREPEND_MAILBOX =  (1 << 4),
00637    OPT_AUTOPLAY =         (1 << 6),
00638    OPT_DTMFEXIT =         (1 << 7),
00639    OPT_MESSAGE_Urgent =   (1 << 8),
00640    OPT_MESSAGE_PRIORITY = (1 << 9)
00641 };
00642 
00643 enum vm_option_args {
00644    OPT_ARG_RECORDGAIN = 0,
00645    OPT_ARG_PLAYFOLDER = 1,
00646    OPT_ARG_DTMFEXIT   = 2,
00647    /* This *must* be the last value in this enum! */
00648    OPT_ARG_ARRAY_SIZE = 3,
00649 };
00650 
00651 enum vm_passwordlocation {
00652    OPT_PWLOC_VOICEMAILCONF = 0,
00653    OPT_PWLOC_SPOOLDIR      = 1,
00654    OPT_PWLOC_USERSCONF     = 2,
00655 };
00656 
00657 AST_APP_OPTIONS(vm_app_options, {
00658    AST_APP_OPTION('s', OPT_SILENT),
00659    AST_APP_OPTION('b', OPT_BUSY_GREETING),
00660    AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
00661    AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
00662    AST_APP_OPTION_ARG('d', OPT_DTMFEXIT, OPT_ARG_DTMFEXIT),
00663    AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
00664    AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
00665    AST_APP_OPTION('U', OPT_MESSAGE_Urgent),
00666    AST_APP_OPTION('P', OPT_MESSAGE_PRIORITY)
00667 });
00668 
00669 static const char * const mailbox_folders[] = {
00670 #ifdef IMAP_STORAGE
00671    imapfolder,
00672 #else
00673    "INBOX",
00674 #endif
00675    "Old",
00676    "Work",
00677    "Family",
00678    "Friends",
00679    "Cust1",
00680    "Cust2",
00681    "Cust3",
00682    "Cust4",
00683    "Cust5",
00684    "Deleted",
00685    "Urgent",
00686 };
00687 
00688 static int load_config(int reload);
00689 #ifdef TEST_FRAMEWORK
00690 static int load_config_from_memory(int reload, struct ast_config *cfg, struct ast_config *ucfg);
00691 #endif
00692 static int actual_load_config(int reload, struct ast_config *cfg, struct ast_config *ucfg);
00693 
00694 /*! \page vmlang Voicemail Language Syntaxes Supported
00695 
00696    \par Syntaxes supported, not really language codes.
00697    \arg \b en    - English
00698    \arg \b de    - German
00699    \arg \b es    - Spanish
00700    \arg \b fr    - French
00701    \arg \b it    - Italian
00702    \arg \b nl    - Dutch
00703    \arg \b pt    - Portuguese
00704    \arg \b pt_BR - Portuguese (Brazil)
00705    \arg \b gr    - Greek
00706    \arg \b no    - Norwegian
00707    \arg \b se    - Swedish
00708    \arg \b tw    - Chinese (Taiwan)
00709    \arg \b ua - Ukrainian
00710 
00711 German requires the following additional soundfile:
00712 \arg \b 1F  einE (feminine)
00713 
00714 Spanish requires the following additional soundfile:
00715 \arg \b 1M      un (masculine)
00716 
00717 Dutch, Portuguese & Spanish require the following additional soundfiles:
00718 \arg \b vm-INBOXs singular of 'new'
00719 \arg \b vm-Olds      singular of 'old/heard/read'
00720 
00721 NB these are plural:
00722 \arg \b vm-INBOX  nieuwe (nl)
00723 \arg \b vm-Old    oude (nl)
00724 
00725 Polish uses:
00726 \arg \b vm-new-a  'new', feminine singular accusative
00727 \arg \b vm-new-e  'new', feminine plural accusative
00728 \arg \b vm-new-ych   'new', feminine plural genitive
00729 \arg \b vm-old-a  'old', feminine singular accusative
00730 \arg \b vm-old-e  'old', feminine plural accusative
00731 \arg \b vm-old-ych   'old', feminine plural genitive
00732 \arg \b digits/1-a   'one', not always same as 'digits/1'
00733 \arg \b digits/2-ie  'two', not always same as 'digits/2'
00734 
00735 Swedish uses:
00736 \arg \b vm-nytt      singular of 'new'
00737 \arg \b vm-nya    plural of 'new'
00738 \arg \b vm-gammalt   singular of 'old'
00739 \arg \b vm-gamla  plural of 'old'
00740 \arg \b digits/ett   'one', not always same as 'digits/1'
00741 
00742 Norwegian uses:
00743 \arg \b vm-ny     singular of 'new'
00744 \arg \b vm-nye    plural of 'new'
00745 \arg \b vm-gammel singular of 'old'
00746 \arg \b vm-gamle  plural of 'old'
00747 
00748 Dutch also uses:
00749 \arg \b nl-om     'at'?
00750 
00751 Spanish also uses:
00752 \arg \b vm-youhaveno
00753 
00754 Italian requires the following additional soundfile:
00755 
00756 For vm_intro_it:
00757 \arg \b vm-nuovo  new
00758 \arg \b vm-nuovi  new plural
00759 \arg \b vm-vecchio   old
00760 \arg \b vm-vecchi old plural
00761 
00762 Japanese requires the following additional soundfile:
00763 \arg \b jp-arimasu          there is
00764 \arg \b jp-arimasen         there is not
00765 \arg \b jp-oshitekudasai    please press
00766 \arg \b jp-ni               article ni
00767 \arg \b jp-ga               article ga
00768 \arg \b jp-wa               article wa
00769 \arg \b jp-wo               article wo
00770 
00771 Chinese (Taiwan) requires the following additional soundfile:
00772 \arg \b vm-tong      A class-word for call (tong1)
00773 \arg \b vm-ri     A class-word for day (ri4)
00774 \arg \b vm-you    You (ni3)
00775 \arg \b vm-haveno   Have no (mei2 you3)
00776 \arg \b vm-have     Have (you3)
00777 \arg \b vm-listen   To listen (yao4 ting1)
00778 
00779 
00780 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
00781 spelled among others when you have to change folder. For the above reasons, vm-INBOX
00782 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
00783 
00784 */
00785 
00786 struct baseio {
00787    int iocp;
00788    int iolen;
00789    int linelength;
00790    int ateof;
00791    unsigned char iobuf[BASEMAXINLINE];
00792 };
00793 
00794 /*! Structure for linked list of users 
00795  * Use ast_vm_user_destroy() to free one of these structures. */
00796 struct ast_vm_user {
00797    char context[AST_MAX_CONTEXT];   /*!< Voicemail context */
00798    char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
00799    char password[80];               /*!< Secret pin code, numbers only */
00800    char fullname[80];               /*!< Full name, for directory app */
00801    char *email;                     /*!< E-mail address */
00802    char *emailsubject;              /*!< E-mail subject */
00803    char *emailbody;                 /*!< E-mail body */
00804    char pager[80];                  /*!< E-mail address to pager (no attachment) */
00805    char serveremail[80];            /*!< From: Mail address */
00806    char language[MAX_LANGUAGE];     /*!< Config: Language setting */
00807    char zonetag[80];                /*!< Time zone */
00808    char locale[20];                 /*!< The locale (for presentation of date/time) */
00809    char callback[80];
00810    char dialout[80];
00811    char uniqueid[80];               /*!< Unique integer identifier */
00812    char exit[80];
00813    char attachfmt[20];              /*!< Attachment format */
00814    unsigned int flags;              /*!< VM_ flags */ 
00815    int saydurationm;
00816    int minsecs;                     /*!< Minimum number of seconds per message for this mailbox */
00817    int maxmsg;                      /*!< Maximum number of msgs per folder for this mailbox */
00818    int maxdeletedmsg;               /*!< Maximum number of deleted msgs saved for this mailbox */
00819    int maxsecs;                     /*!< Maximum number of seconds per message for this mailbox */
00820    int passwordlocation;            /*!< Storage location of the password */
00821 #ifdef IMAP_STORAGE
00822    char imapserver[48];             /*!< IMAP server address */
00823    char imapport[8];                /*!< IMAP server port */
00824    char imapflags[128];             /*!< IMAP optional flags */
00825    char imapuser[80];               /*!< IMAP server login */
00826    char imappassword[80];           /*!< IMAP server password if authpassword not defined */
00827    char imapfolder[64];             /*!< IMAP voicemail folder */
00828    char imapvmshareid[80];          /*!< Shared mailbox ID to use rather than the dialed one */
00829    int imapversion;                 /*!< If configuration changes, use the new values */
00830 #endif
00831    double volgain;                  /*!< Volume gain for voicemails sent via email */
00832    AST_LIST_ENTRY(ast_vm_user) list;
00833 };
00834 
00835 /*! Voicemail time zones */
00836 struct vm_zone {
00837    AST_LIST_ENTRY(vm_zone) list;
00838    char name[80];
00839    char timezone[80];
00840    char msg_format[512];
00841 };
00842 
00843 #define VMSTATE_MAX_MSG_ARRAY 256
00844 
00845 /*! Voicemail mailbox state */
00846 struct vm_state {
00847    char curbox[80];
00848    char username[80];
00849    char context[80];
00850    char curdir[PATH_MAX];
00851    char vmbox[PATH_MAX];
00852    char fn[PATH_MAX];
00853    char intro[PATH_MAX];
00854    int *deleted;
00855    int *heard;
00856    int dh_arraysize; /* used for deleted / heard allocation */
00857    int curmsg;
00858    int lastmsg;
00859    int newmessages;
00860    int oldmessages;
00861    int urgentmessages;
00862    int starting;
00863    int repeats;
00864 #ifdef IMAP_STORAGE
00865    ast_mutex_t lock;
00866    int updated;                         /*!< decremented on each mail check until 1 -allows delay */
00867    long *msgArray;
00868    unsigned msg_array_max;
00869    MAILSTREAM *mailstream;
00870    int vmArrayIndex;
00871    char imapuser[80];                   /*!< IMAP server login */
00872    char imapfolder[64];                 /*!< IMAP voicemail folder */
00873    char imapserver[48];                 /*!< IMAP server address */
00874    char imapport[8];                    /*!< IMAP server port */
00875    char imapflags[128];                 /*!< IMAP optional flags */
00876    int imapversion;
00877    int interactive;
00878    char introfn[PATH_MAX];              /*!< Name of prepended file */
00879    unsigned int quota_limit;
00880    unsigned int quota_usage;
00881    struct vm_state *persist_vms;
00882 #endif
00883 };
00884 
00885 #ifdef ODBC_STORAGE
00886 static char odbc_database[80];
00887 static char odbc_table[80];
00888 #define RETRIEVE(a,b,c,d) retrieve_file(a,b)
00889 #define DISPOSE(a,b) remove_file(a,b)
00890 #define STORE(a,b,c,d,e,f,g,h,i,j,k) store_file(a,b,c,d)
00891 #define EXISTS(a,b,c,d) (message_exists(a,b))
00892 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
00893 #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
00894 #define DELETE(a,b,c,d) (delete_file(a,b))
00895 #define UPDATE_MSG_ID(a, b, c, d, e, f) (odbc_update_msg_id((a), (b), (c)))
00896 #else
00897 #ifdef IMAP_STORAGE
00898 #define DISPOSE(a,b) (imap_remove_file(a,b))
00899 #define STORE(a,b,c,d,e,f,g,h,i,j,k) (imap_store_file(a,b,c,d,e,f,g,h,i,j,k))
00900 #define RETRIEVE(a,b,c,d) imap_retrieve_file(a,b,c,d)
00901 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
00902 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
00903 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
00904 #define DELETE(a,b,c,d) (vm_imap_delete(a,b,d))
00905 #define UPDATE_MSG_ID(a, b, c, d, e, f) (vm_imap_update_msg_id((a), (b), (c), (d), (e), (f)))
00906 #else
00907 #define RETRIEVE(a,b,c,d)
00908 #define DISPOSE(a,b)
00909 #define STORE(a,b,c,d,e,f,g,h,i,j,k)
00910 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
00911 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
00912 #define COPY(a,b,c,d,e,f,g,h) (copy_plain_file(g,h)); 
00913 #define DELETE(a,b,c,d) (vm_delete(c))
00914 #define UPDATE_MSG_ID(a, b, c, d, e, f)
00915 #endif
00916 #endif
00917 
00918 static char VM_SPOOL_DIR[PATH_MAX];
00919 
00920 static char ext_pass_cmd[128];
00921 static char ext_pass_check_cmd[128];
00922 
00923 static int my_umask;
00924 
00925 #define PWDCHANGE_INTERNAL (1 << 1)
00926 #define PWDCHANGE_EXTERNAL (1 << 2)
00927 static int pwdchange = PWDCHANGE_INTERNAL;
00928 
00929 #ifdef ODBC_STORAGE
00930 #define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
00931 #else
00932 # ifdef IMAP_STORAGE
00933 # define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
00934 # else
00935 # define tdesc "Comedian Mail (Voicemail System)"
00936 # endif
00937 #endif
00938 
00939 static char userscontext[AST_MAX_EXTENSION] = "default";
00940 
00941 static char *addesc = "Comedian Mail";
00942 
00943 /* Leave a message */
00944 static char *app = "VoiceMail";
00945 
00946 /* Check mail, control, etc */
00947 static char *app2 = "VoiceMailMain";
00948 
00949 static char *app3 = "MailboxExists";
00950 static char *app4 = "VMAuthenticate";
00951 
00952 static char *playmsg_app = "VoiceMailPlayMsg";
00953 
00954 static char *sayname_app = "VMSayName";
00955 
00956 static AST_LIST_HEAD_STATIC(users, ast_vm_user);
00957 static AST_LIST_HEAD_STATIC(zones, vm_zone);
00958 static char zonetag[80];
00959 static char locale[20];
00960 static int maxsilence;
00961 static int maxmsg;
00962 static int maxdeletedmsg;
00963 static int silencethreshold = 128;
00964 static char serveremail[80];
00965 static char mailcmd[160];  /* Configurable mail cmd */
00966 static char externnotify[160]; 
00967 static struct ast_smdi_interface *smdi_iface = NULL;
00968 static char vmfmts[80];
00969 static double volgain;
00970 static int vmminsecs;
00971 static int vmmaxsecs;
00972 static int maxgreet;
00973 static int skipms;
00974 static int maxlogins;
00975 static int minpassword;
00976 static int passwordlocation;
00977 
00978 /*! Poll mailboxes for changes since there is something external to
00979  *  app_voicemail that may change them. */
00980 static unsigned int poll_mailboxes;
00981 
00982 /*! Polling frequency */
00983 static unsigned int poll_freq;
00984 /*! By default, poll every 30 seconds */
00985 #define DEFAULT_POLL_FREQ 30
00986 
00987 AST_MUTEX_DEFINE_STATIC(poll_lock);
00988 static ast_cond_t poll_cond = PTHREAD_COND_INITIALIZER;
00989 static pthread_t poll_thread = AST_PTHREADT_NULL;
00990 static unsigned char poll_thread_run;
00991 
00992 /*! Subscription to MWI event subscription changes */
00993 static struct stasis_subscription *mwi_sub_sub;
00994 
00995 /*!
00996  * \brief An MWI subscription
00997  *
00998  * This is so we can keep track of which mailboxes are subscribed to.
00999  * This way, we know which mailboxes to poll when the pollmailboxes
01000  * option is being used.
01001  */
01002 struct mwi_sub {
01003    AST_RWLIST_ENTRY(mwi_sub) entry;
01004    int old_urgent;
01005    int old_new;
01006    int old_old;
01007    char *uniqueid;
01008    char mailbox[1];
01009 };
01010 
01011 struct mwi_sub_task {
01012    const char *mailbox;
01013    const char *context;
01014    const char *uniqueid;
01015 };
01016 
01017 static void mwi_sub_task_dtor(struct mwi_sub_task *mwist)
01018 {
01019    ast_free((void *) mwist->mailbox);
01020    ast_free((void *) mwist->context);
01021    ast_free((void *) mwist->uniqueid);
01022    ast_free(mwist);
01023 }
01024 
01025 static struct ast_taskprocessor *mwi_subscription_tps;
01026 
01027 static AST_RWLIST_HEAD_STATIC(mwi_subs, mwi_sub);
01028 
01029 /* custom audio control prompts for voicemail playback */
01030 static char listen_control_forward_key[12];
01031 static char listen_control_reverse_key[12];
01032 static char listen_control_pause_key[12];
01033 static char listen_control_restart_key[12];
01034 static char listen_control_stop_key[12];
01035 
01036 /* custom password sounds */
01037 static char vm_password[80] = "vm-password";
01038 static char vm_newpassword[80] = "vm-newpassword";
01039 static char vm_passchanged[80] = "vm-passchanged";
01040 static char vm_reenterpassword[80] = "vm-reenterpassword";
01041 static char vm_mismatch[80] = "vm-mismatch";
01042 static char vm_invalid_password[80] = "vm-invalid-password";
01043 static char vm_pls_try_again[80] = "vm-pls-try-again";
01044 
01045 /*
01046  * XXX If we have the time, motivation, etc. to fix up this prompt, one of the following would be appropriate:
01047  * 1. create a sound along the lines of "Please try again.  When done, press the pound key" which could be spliced
01048  * from existing sound clips.  This would require some programming changes in the area of vm_forward options and also
01049  * app.c's __ast_play_and_record function
01050  * 2. create a sound prompt saying "Please try again.  When done recording, press any key to stop and send the prepended
01051  * message."  At the time of this comment, I think this would require new voice work to be commissioned.
01052  * 3. Something way different like providing instructions before a time out or a post-recording menu.  This would require
01053  * more effort than either of the other two.
01054  */
01055 static char vm_prepend_timeout[80] = "vm-then-pound";
01056 
01057 static struct ast_flags globalflags = {0};
01058 
01059 static int saydurationminfo;
01060 
01061 static char dialcontext[AST_MAX_CONTEXT] = "";
01062 static char callcontext[AST_MAX_CONTEXT] = "";
01063 static char exitcontext[AST_MAX_CONTEXT] = "";
01064 
01065 static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
01066 
01067 
01068 static char *emailbody = NULL;
01069 static char *emailsubject = NULL;
01070 static char *pagerbody = NULL;
01071 static char *pagersubject = NULL;
01072 static char fromstring[100];
01073 static char pagerfromstring[100];
01074 static char charset[32] = "ISO-8859-1";
01075 
01076 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
01077 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
01078 static int adsiver = 1;
01079 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
01080 static char pagerdateformat[32] = "%A, %B %d, %Y at %r";
01081 
01082 /* Forward declarations - generic */
01083 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
01084 static int close_mailbox(struct vm_state *vms, struct ast_vm_user *vmu);
01085 static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msg, int option, signed char record_gain);
01086 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
01087 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
01088          char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, int *sound_duration, const char *unlockdir,
01089          signed char record_gain, struct vm_state *vms, char *flag, const char *msg_id);
01090 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
01091 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
01092 static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msgnum, long duration, char *fmt, char *cidnum, char *cidname, const char *flag);
01093 static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, const char *fromfolder, char *cidnum, char *cidname, char *attach, char *attach2, char *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category, int imap, const char *flag, const char *msg_id);
01094 static void apply_options(struct ast_vm_user *vmu, const char *options);
01095 static int add_email_attachment(FILE *p, struct ast_vm_user *vmu, char *format, char *attach, char *greeting_attachment, char *mailbox, char *bound, char *filename, int last, int msgnum);
01096 static int is_valid_dtmf(const char *key);
01097 static void read_password_from_file(const char *secretfn, char *password, int passwordlen);
01098 static int write_password_to_file(const char *secretfn, const char *password);
01099 static const char *substitute_escapes(const char *value);
01100 static int message_range_and_existence_check(struct vm_state *vms, const char *msg_ids [], size_t num_msgs, int *msg_nums, struct ast_vm_user *vmu);
01101 static void notify_new_state(struct ast_vm_user *vmu);
01102 
01103 /*!
01104  * Place a message in the indicated folder
01105  *
01106  * \param vmu Voicemail user
01107  * \param vms Current voicemail state for the user
01108  * \param msg The message number to save
01109  * \param box The folder into which the message should be saved
01110  * \param[out] newmsg The new message number of the saved message
01111  * \param move Tells whether to copy or to move the message
01112  *
01113  * \note the "move" parameter is only honored for IMAP voicemail presently
01114  * \retval 0 Success
01115  * \retval other Failure
01116  */
01117 static int save_to_folder(struct ast_vm_user *vmu, struct vm_state *vms, int msg, int box, int *newmsg, int move);
01118 
01119 static struct ast_vm_mailbox_snapshot *vm_mailbox_snapshot_create(const char *mailbox, const char *context, const char *folder, int descending, enum ast_vm_snapshot_sort_val sort_val, int combine_INBOX_and_OLD);
01120 static struct ast_vm_mailbox_snapshot *vm_mailbox_snapshot_destroy(struct ast_vm_mailbox_snapshot *mailbox_snapshot);
01121 
01122 static int vm_msg_forward(const char *from_mailbox, const char *from_context, const char *from_folder, const char *to_mailbox, const char *to_context, const char *to_folder, size_t num_msgs, const char *msg_ids[], int delete_old);
01123 static int vm_msg_move(const char *mailbox, const char *context, size_t num_msgs, const char *oldfolder, const char *old_msg_ids[], const char *newfolder);
01124 static int vm_msg_remove(const char *mailbox, const char *context, size_t num_msgs, const char *folder, const char *msgs[]);
01125 static int vm_msg_play(struct ast_channel *chan, const char *mailbox, const char *context, const char *folder, const char *msg_num, ast_vm_msg_play_cb cb);
01126 
01127 #ifdef TEST_FRAMEWORK
01128 static int vm_test_destroy_user(const char *context, const char *mailbox);
01129 static int vm_test_create_user(const char *context, const char *mailbox);
01130 #endif
01131 
01132 /*!
01133  * \internal
01134  * \brief Parse the given mailbox_id into mailbox and context.
01135  * \since 12.0.0
01136  *
01137  * \param mailbox_id The mailbox@context string to separate.
01138  * \param mailbox Where the mailbox part will start.
01139  * \param context Where the context part will start.  ("default" if not present)
01140  *
01141  * \retval 0 on success.
01142  * \retval -1 on error.
01143  */
01144 static int separate_mailbox(char *mailbox_id, char **mailbox, char **context)
01145 {
01146    if (ast_strlen_zero(mailbox_id) || !mailbox || !context) {
01147       return -1;
01148    }
01149    *context = mailbox_id;
01150    *mailbox = strsep(context, "@");
01151    if (ast_strlen_zero(*mailbox)) {
01152       return -1;
01153    }
01154    if (ast_strlen_zero(*context)) {
01155       *context = "default";
01156    }
01157    return 0;
01158 }
01159 
01160 struct ao2_container *inprocess_container;
01161 
01162 struct inprocess {
01163    int count;
01164    char *context;
01165    char mailbox[0];
01166 };
01167 
01168 static int inprocess_hash_fn(const void *obj, const int flags)
01169 {
01170    const struct inprocess *i = obj;
01171    return atoi(i->mailbox);
01172 }
01173 
01174 static int inprocess_cmp_fn(void *obj, void *arg, int flags)
01175 {
01176    struct inprocess *i = obj, *j = arg;
01177    if (strcmp(i->mailbox, j->mailbox)) {
01178       return 0;
01179    }
01180    return !strcmp(i->context, j->context) ? CMP_MATCH : 0;
01181 }
01182 
01183 static int inprocess_count(const char *context, const char *mailbox, int delta)
01184 {
01185    struct inprocess *i, *arg = ast_alloca(sizeof(*arg) + strlen(context) + strlen(mailbox) + 2);
01186    arg->context = arg->mailbox + strlen(mailbox) + 1;
01187    strcpy(arg->mailbox, mailbox); /* SAFE */
01188    strcpy(arg->context, context); /* SAFE */
01189    ao2_lock(inprocess_container);
01190    if ((i = ao2_find(inprocess_container, arg, 0))) {
01191       int ret = ast_atomic_fetchadd_int(&i->count, delta);
01192       ao2_unlock(inprocess_container);
01193       ao2_ref(i, -1);
01194       return ret;
01195    }
01196    if (delta < 0) {
01197       ast_log(LOG_WARNING, "BUG: ref count decrement on non-existing object???\n");
01198    }
01199    if (!(i = ao2_alloc(sizeof(*i) + strlen(context) + strlen(mailbox) + 2, NULL))) {
01200       ao2_unlock(inprocess_container);
01201       return 0;
01202    }
01203    i->context = i->mailbox + strlen(mailbox) + 1;
01204    strcpy(i->mailbox, mailbox); /* SAFE */
01205    strcpy(i->context, context); /* SAFE */
01206    i->count = delta;
01207    ao2_link(inprocess_container, i);
01208    ao2_unlock(inprocess_container);
01209    ao2_ref(i, -1);
01210    return 0;
01211 }
01212 
01213 #if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
01214 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
01215 #endif
01216 
01217 /*!
01218  * \brief Strips control and non 7-bit clean characters from input string.
01219  *
01220  * \note To map control and none 7-bit characters to a 7-bit clean characters
01221  *  please use ast_str_encode_mine().
01222  */
01223 static char *strip_control_and_high(const char *input, char *buf, size_t buflen)
01224 {
01225    char *bufptr = buf;
01226    for (; *input; input++) {
01227       if (*input < 32) {
01228          continue;
01229       }
01230       *bufptr++ = *input;
01231       if (bufptr == buf + buflen - 1) {
01232          break;
01233       }
01234    }
01235    *bufptr = '\0';
01236    return buf;
01237 }
01238 
01239 
01240 /*!
01241  * \brief Sets default voicemail system options to a voicemail user.
01242  *
01243  * This applies select global settings to a newly created (dynamic) instance of a voicemail user.
01244  * - all the globalflags
01245  * - the saydurationminfo
01246  * - the callcontext
01247  * - the dialcontext
01248  * - the exitcontext
01249  * - vmmaxsecs, vmmaxmsg, maxdeletedmsg
01250  * - volume gain.
01251  * - emailsubject, emailbody set to NULL
01252  */
01253 static void populate_defaults(struct ast_vm_user *vmu)
01254 {
01255    ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
01256    vmu->passwordlocation = passwordlocation;
01257    if (saydurationminfo) {
01258       vmu->saydurationm = saydurationminfo;
01259    }
01260    ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
01261    ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
01262    ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
01263    ast_copy_string(vmu->zonetag, zonetag, sizeof(vmu->zonetag));
01264    ast_copy_string(vmu->locale, locale, sizeof(vmu->locale));
01265    if (vmminsecs) {
01266       vmu->minsecs = vmminsecs;
01267    }
01268    if (vmmaxsecs) {
01269       vmu->maxsecs = vmmaxsecs;
01270    }
01271    if (maxmsg) {
01272       vmu->maxmsg = maxmsg;
01273    }
01274    if (maxdeletedmsg) {
01275       vmu->maxdeletedmsg = maxdeletedmsg;
01276    }
01277    vmu->volgain = volgain;
01278    ast_free(vmu->email);
01279    vmu->email = NULL;
01280    ast_free(vmu->emailsubject);
01281    vmu->emailsubject = NULL;
01282    ast_free(vmu->emailbody);
01283    vmu->emailbody = NULL;
01284 #ifdef IMAP_STORAGE
01285    ast_copy_string(vmu->imapfolder, imapfolder, sizeof(vmu->imapfolder));
01286    ast_copy_string(vmu->imapserver, imapserver, sizeof(vmu->imapserver));
01287    ast_copy_string(vmu->imapport, imapport, sizeof(vmu->imapport));
01288    ast_copy_string(vmu->imapflags, imapflags, sizeof(vmu->imapflags));
01289 #endif
01290 }
01291 
01292 /*!
01293  * \brief Sets a a specific property value.
01294  * \param vmu The voicemail user object to work with.
01295  * \param var The name of the property to be set.
01296  * \param value The value to be set to the property.
01297  * 
01298  * The property name must be one of the understood properties. See the source for details.
01299  */
01300 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
01301 {
01302    int x;
01303    if (!strcasecmp(var, "attach")) {
01304       ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
01305    } else if (!strcasecmp(var, "attachfmt")) {
01306       ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
01307    } else if (!strcasecmp(var, "serveremail")) {
01308       ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
01309    } else if (!strcasecmp(var, "emailbody")) {
01310       ast_free(vmu->emailbody);
01311       vmu->emailbody = ast_strdup(substitute_escapes(value));
01312    } else if (!strcasecmp(var, "emailsubject")) {
01313       ast_free(vmu->emailsubject);
01314       vmu->emailsubject = ast_strdup(substitute_escapes(value));
01315    } else if (!strcasecmp(var, "language")) {
01316       ast_copy_string(vmu->language, value, sizeof(vmu->language));
01317    } else if (!strcasecmp(var, "tz")) {
01318       ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
01319    } else if (!strcasecmp(var, "locale")) {
01320       ast_copy_string(vmu->locale, value, sizeof(vmu->locale));
01321 #ifdef IMAP_STORAGE
01322    } else if (!strcasecmp(var, "imapuser")) {
01323       ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
01324       vmu->imapversion = imapversion;
01325    } else if (!strcasecmp(var, "imapserver")) {
01326       ast_copy_string(vmu->imapserver, value, sizeof(vmu->imapserver));
01327       vmu->imapversion = imapversion;
01328    } else if (!strcasecmp(var, "imapport")) {
01329       ast_copy_string(vmu->imapport, value, sizeof(vmu->imapport));
01330       vmu->imapversion = imapversion;
01331    } else if (!strcasecmp(var, "imapflags")) {
01332       ast_copy_string(vmu->imapflags, value, sizeof(vmu->imapflags));
01333       vmu->imapversion = imapversion;
01334    } else if (!strcasecmp(var, "imappassword") || !strcasecmp(var, "imapsecret")) {
01335       ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
01336       vmu->imapversion = imapversion;
01337    } else if (!strcasecmp(var, "imapfolder")) {
01338       ast_copy_string(vmu->imapfolder, value, sizeof(vmu->imapfolder));
01339       vmu->imapversion = imapversion;
01340    } else if (!strcasecmp(var, "imapvmshareid")) {
01341       ast_copy_string(vmu->imapvmshareid, value, sizeof(vmu->imapvmshareid));
01342       vmu->imapversion = imapversion;
01343 #endif
01344    } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
01345       ast_set2_flag(vmu, ast_true(value), VM_DELETE); 
01346    } else if (!strcasecmp(var, "saycid")){
01347       ast_set2_flag(vmu, ast_true(value), VM_SAYCID); 
01348    } else if (!strcasecmp(var, "sendvoicemail")){
01349       ast_set2_flag(vmu, ast_true(value), VM_SVMAIL); 
01350    } else if (!strcasecmp(var, "review")){
01351       ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
01352    } else if (!strcasecmp(var, "tempgreetwarn")){
01353       ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);   
01354    } else if (!strcasecmp(var, "messagewrap")){
01355       ast_set2_flag(vmu, ast_true(value), VM_MESSAGEWRAP);  
01356    } else if (!strcasecmp(var, "operator")) {
01357       ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);  
01358    } else if (!strcasecmp(var, "envelope")){
01359       ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);  
01360    } else if (!strcasecmp(var, "moveheard")){
01361       ast_set2_flag(vmu, ast_true(value), VM_MOVEHEARD);
01362    } else if (!strcasecmp(var, "sayduration")){
01363       ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);  
01364    } else if (!strcasecmp(var, "saydurationm")){
01365       if (sscanf(value, "%30d", &x) == 1) {
01366          vmu->saydurationm = x;
01367       } else {
01368          ast_log(AST_LOG_WARNING, "Invalid min duration for say duration\n");
01369       }
01370    } else if (!strcasecmp(var, "forcename")){
01371       ast_set2_flag(vmu, ast_true(value), VM_FORCENAME); 
01372    } else if (!strcasecmp(var, "forcegreetings")){
01373       ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);   
01374    } else if (!strcasecmp(var, "callback")) {
01375       ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
01376    } else if (!strcasecmp(var, "dialout")) {
01377       ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
01378    } else if (!strcasecmp(var, "exitcontext")) {
01379       ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
01380    } else if (!strcasecmp(var, "minsecs")) {
01381       if (sscanf(value, "%30d", &x) == 1 && x >= 0) {
01382          vmu->minsecs = x;
01383       } else {
01384          ast_log(LOG_WARNING, "Invalid min message length of %s. Using global value %d\n", value, vmminsecs);
01385          vmu->minsecs = vmminsecs;
01386       }
01387    } else if (!strcasecmp(var, "maxmessage") || !strcasecmp(var, "maxsecs")) {
01388       vmu->maxsecs = atoi(value);
01389       if (vmu->maxsecs <= 0) {
01390          ast_log(AST_LOG_WARNING, "Invalid max message length of %s. Using global value %d\n", value, vmmaxsecs);
01391          vmu->maxsecs = vmmaxsecs;
01392       } else {
01393          vmu->maxsecs = atoi(value);
01394       }
01395       if (!strcasecmp(var, "maxmessage"))
01396          ast_log(AST_LOG_WARNING, "Option 'maxmessage' has been deprecated in favor of 'maxsecs'.  Please make that change in your voicemail config.\n");
01397    } else if (!strcasecmp(var, "maxmsg")) {
01398       vmu->maxmsg = atoi(value);
01399       /* Accept maxmsg=0 (Greetings only voicemail) */
01400       if (vmu->maxmsg < 0) {
01401          ast_log(AST_LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %d\n", value, MAXMSG);
01402          vmu->maxmsg = MAXMSG;
01403       } else if (vmu->maxmsg > MAXMSGLIMIT) {
01404          ast_log(AST_LOG_WARNING, "Maximum number of messages per folder is %d. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
01405          vmu->maxmsg = MAXMSGLIMIT;
01406       }
01407    } else if (!strcasecmp(var, "nextaftercmd")) {
01408       ast_set2_flag(vmu, ast_true(value), VM_SKIPAFTERCMD);
01409    } else if (!strcasecmp(var, "backupdeleted")) {
01410       if (sscanf(value, "%30d", &x) == 1)
01411          vmu->maxdeletedmsg = x;
01412       else if (ast_true(value))
01413          vmu->maxdeletedmsg = MAXMSG;
01414       else
01415          vmu->maxdeletedmsg = 0;
01416 
01417       if (vmu->maxdeletedmsg < 0) {
01418          ast_log(AST_LOG_WARNING, "Invalid number of deleted messages saved per mailbox backupdeleted=%s. Using default value %d\n", value, MAXMSG);
01419          vmu->maxdeletedmsg = MAXMSG;
01420       } else if (vmu->maxdeletedmsg > MAXMSGLIMIT) {
01421          ast_log(AST_LOG_WARNING, "Maximum number of deleted messages saved per mailbox is %d. Cannot accept value backupdeleted=%s\n", MAXMSGLIMIT, value);
01422          vmu->maxdeletedmsg = MAXMSGLIMIT;
01423       }
01424    } else if (!strcasecmp(var, "volgain")) {
01425       sscanf(value, "%30lf", &vmu->volgain);
01426    } else if (!strcasecmp(var, "passwordlocation")) {
01427       if (!strcasecmp(value, "spooldir")) {
01428          vmu->passwordlocation = OPT_PWLOC_SPOOLDIR;
01429       } else {
01430          vmu->passwordlocation = OPT_PWLOC_VOICEMAILCONF;
01431       }
01432    } else if (!strcasecmp(var, "options")) {
01433       apply_options(vmu, value);
01434    }
01435 }
01436 
01437 static char *vm_check_password_shell(char *command, char *buf, size_t len) 
01438 {
01439    int fds[2], pid = 0;
01440 
01441    memset(buf, 0, len);
01442 
01443    if (pipe(fds)) {
01444       snprintf(buf, len, "FAILURE: Pipe failed: %s", strerror(errno));
01445    } else {
01446       /* good to go*/
01447       pid = ast_safe_fork(0);
01448 
01449       if (pid < 0) {
01450          /* ok maybe not */
01451          close(fds[0]);
01452          close(fds[1]);
01453          snprintf(buf, len, "FAILURE: Fork failed");
01454       } else if (pid) {
01455          /* parent */
01456          close(fds[1]);
01457          if (read(fds[0], buf, len) < 0) {
01458             ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
01459          }
01460          close(fds[0]);
01461       } else {
01462          /*  child */
01463          AST_DECLARE_APP_ARGS(arg,
01464             AST_APP_ARG(v)[20];
01465          );
01466          char *mycmd = ast_strdupa(command);
01467 
01468          close(fds[0]);
01469          dup2(fds[1], STDOUT_FILENO);
01470          close(fds[1]);
01471          ast_close_fds_above_n(STDOUT_FILENO);
01472 
01473          AST_NONSTANDARD_APP_ARGS(arg, mycmd, ' ');
01474 
01475          execv(arg.v[0], arg.v); 
01476          printf("FAILURE: %s", strerror(errno));
01477          _exit(0);
01478       }
01479    }
01480    return buf;
01481 }
01482 
01483 /*!
01484  * \brief Check that password meets minimum required length
01485  * \param vmu The voicemail user to change the password for.
01486  * \param password The password string to check
01487  *
01488  * \return zero on ok, 1 on not ok.
01489  */
01490 static int check_password(struct ast_vm_user *vmu, char *password)
01491 {
01492    /* check minimum length */
01493    if (strlen(password) < minpassword)
01494       return 1;
01495    /* check that password does not contain '*' character */
01496    if (!ast_strlen_zero(password) && password[0] == '*')
01497       return 1;
01498    if (!ast_strlen_zero(ext_pass_check_cmd)) {
01499       char cmd[255], buf[255];
01500 
01501       ast_debug(1, "Verify password policies for %s\n", password);
01502 
01503       snprintf(cmd, sizeof(cmd), "%s %s %s %s %s", ext_pass_check_cmd, vmu->mailbox, vmu->context, vmu->password, password);
01504       if (vm_check_password_shell(cmd, buf, sizeof(buf))) {
01505          ast_debug(5, "Result: %s\n", buf);
01506          if (!strncasecmp(buf, "VALID", 5)) {
01507             ast_debug(3, "Passed password check: '%s'\n", buf);
01508             return 0;
01509          } else if (!strncasecmp(buf, "FAILURE", 7)) {
01510             ast_log(AST_LOG_WARNING, "Unable to execute password validation script: '%s'.\n", buf);
01511             return 0;
01512          } else {
01513             ast_log(AST_LOG_NOTICE, "Password doesn't match policies for user %s %s\n", vmu->mailbox, password);
01514             return 1;
01515          }
01516       }
01517    }
01518    return 0;
01519 }
01520 
01521 /*! 
01522  * \brief Performs a change of the voicemail passowrd in the realtime engine.
01523  * \param vmu The voicemail user to change the password for.
01524  * \param password The new value to be set to the password for this user.
01525  * 
01526  * This only works if there is a realtime engine configured.
01527  * This is called from the (top level) vm_change_password.
01528  *
01529  * \return zero on success, -1 on error.
01530  */
01531 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
01532 {
01533    int res = -1;
01534    if (!strcmp(vmu->password, password)) {
01535       /* No change (but an update would return 0 rows updated, so we opt out here) */
01536       return 0;
01537    }
01538 
01539    if (strlen(password) > 10) {
01540       ast_realtime_require_field("voicemail", "password", RQ_CHAR, strlen(password), SENTINEL);
01541    }
01542    if (ast_update2_realtime("voicemail", "context", vmu->context, "mailbox", vmu->mailbox, SENTINEL, "password", password, SENTINEL) > 0) {
01543       ast_test_suite_event_notify("PASSWORDCHANGED", "Message: realtime engine updated with new password\r\nPasswordSource: realtime");
01544       ast_copy_string(vmu->password, password, sizeof(vmu->password));
01545       res = 0;
01546    }
01547    return res;
01548 }
01549 
01550 /*!
01551  * \brief Destructively Parse options and apply.
01552  */
01553 static void apply_options(struct ast_vm_user *vmu, const char *options)
01554 {  
01555    char *stringp;
01556    char *s;
01557    char *var, *value;
01558    stringp = ast_strdupa(options);
01559    while ((s = strsep(&stringp, "|"))) {
01560       value = s;
01561       if ((var = strsep(&value, "=")) && value) {
01562          apply_option(vmu, var, value);
01563       }
01564    }  
01565 }
01566 
01567 /*!
01568  * \brief Loads the options specific to a voicemail user.
01569  * 
01570  * This is called when a vm_user structure is being set up, such as from load_options.
01571  */
01572 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
01573 {
01574    for (; var; var = var->next) {
01575       if (!strcasecmp(var->name, "vmsecret")) {
01576          ast_copy_string(retval->password, var->value, sizeof(retval->password));
01577       } else if (!strcasecmp(var->name, "secret") || !strcasecmp(var->name, "password")) { /* don't overwrite vmsecret if it exists */
01578          if (ast_strlen_zero(retval->password)) {
01579             if (!ast_strlen_zero(var->value) && var->value[0] == '*') {
01580                ast_log(LOG_WARNING, "Invalid password detected for mailbox %s.  The password"
01581                   "\n\tmust be reset in voicemail.conf.\n", retval->mailbox);
01582             } else {
01583                ast_copy_string(retval->password, var->value, sizeof(retval->password));
01584             }
01585          }
01586       } else if (!strcasecmp(var->name, "uniqueid")) {
01587          ast_copy_string(retval->uniqueid, var->value, sizeof(retval->uniqueid));
01588       } else if (!strcasecmp(var->name, "pager")) {
01589          ast_copy_string(retval->pager, var->value, sizeof(retval->pager));
01590       } else if (!strcasecmp(var->name, "email")) {
01591          ast_free(retval->email);
01592          retval->email = ast_strdup(var->value);
01593       } else if (!strcasecmp(var->name, "fullname")) {
01594          ast_copy_string(retval->fullname, var->value, sizeof(retval->fullname));
01595       } else if (!strcasecmp(var->name, "context")) {
01596          ast_copy_string(retval->context, var->value, sizeof(retval->context));
01597       } else if (!strcasecmp(var->name, "emailsubject")) {
01598          ast_free(retval->emailsubject);
01599          retval->emailsubject = ast_strdup(substitute_escapes(var->value));
01600       } else if (!strcasecmp(var->name, "emailbody")) {
01601          ast_free(retval->emailbody);
01602          retval->emailbody = ast_strdup(substitute_escapes(var->value));
01603 #ifdef IMAP_STORAGE
01604       } else if (!strcasecmp(var->name, "imapuser")) {
01605          ast_copy_string(retval->imapuser, var->value, sizeof(retval->imapuser));
01606          retval->imapversion = imapversion;
01607       } else if (!strcasecmp(var->name, "imapserver")) {
01608          ast_copy_string(retval->imapserver, var->value, sizeof(retval->imapserver));
01609          retval->imapversion = imapversion;
01610       } else if (!strcasecmp(var->name, "imapport")) {
01611          ast_copy_string(retval->imapport, var->value, sizeof(retval->imapport));
01612          retval->imapversion = imapversion;
01613       } else if (!strcasecmp(var->name, "imapflags")) {
01614          ast_copy_string(retval->imapflags, var->value, sizeof(retval->imapflags));
01615          retval->imapversion = imapversion;
01616       } else if (!strcasecmp(var->name, "imappassword") || !strcasecmp(var->name, "imapsecret")) {
01617          ast_copy_string(retval->imappassword, var->value, sizeof(retval->imappassword));
01618          retval->imapversion = imapversion;
01619       } else if (!strcasecmp(var->name, "imapfolder")) {
01620          ast_copy_string(retval->imapfolder, var->value, sizeof(retval->imapfolder));
01621          retval->imapversion = imapversion;
01622       } else if (!strcasecmp(var->name, "imapvmshareid")) {
01623          ast_copy_string(retval->imapvmshareid, var->value, sizeof(retval->imapvmshareid));
01624          retval->imapversion = imapversion;
01625 #endif
01626       } else
01627          apply_option(retval, var->name, var->value);
01628    }
01629 }
01630 
01631 /*!
01632  * \brief Determines if a DTMF key entered is valid.
01633  * \param key The character to be compared. expects a single character. Though is capable of handling a string, this is internally copies using ast_strdupa.
01634  *
01635  * Tests the character entered against the set of valid DTMF characters. 
01636  * \return 1 if the character entered is a valid DTMF digit, 0 if the character is invalid.
01637  */
01638 static int is_valid_dtmf(const char *key)
01639 {
01640    int i;
01641    char *local_key = ast_strdupa(key);
01642 
01643    for (i = 0; i < strlen(key); ++i) {
01644       if (!strchr(VALID_DTMF, *local_key)) {
01645          ast_log(AST_LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
01646          return 0;
01647       }
01648       local_key++;
01649    }
01650    return 1;
01651 }
01652 
01653 /*!
01654  * \brief Finds a voicemail user from the realtime engine.
01655  * \param ivm
01656  * \param context
01657  * \param mailbox
01658  *
01659  * This is called as a fall through case when the normal find_user() was not able to find a user. That is, the default it so look in the usual voicemail users file first.
01660  *
01661  * \return The ast_vm_user structure for the user that was found.
01662  */
01663 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
01664 {
01665    struct ast_variable *var;
01666    struct ast_vm_user *retval;
01667 
01668    if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
01669       if (ivm) {
01670          memset(retval, 0, sizeof(*retval));
01671       }
01672       populate_defaults(retval);
01673       if (!ivm) {
01674          ast_set_flag(retval, VM_ALLOCED);
01675       }
01676       if (mailbox) {
01677          ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
01678       }
01679       if (!context && ast_test_flag((&globalflags), VM_SEARCH)) {
01680          var = ast_load_realtime("voicemail", "mailbox", mailbox, SENTINEL);
01681       } else {
01682          var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, SENTINEL);
01683       }
01684       if (var) {
01685          apply_options_full(retval, var);
01686          ast_variables_destroy(var);
01687       } else { 
01688          if (!ivm) 
01689             ast_free(retval);
01690          retval = NULL;
01691       }  
01692    } 
01693    return retval;
01694 }
01695 
01696 /*!
01697  * \brief Finds a voicemail user from the users file or the realtime engine.
01698  * \param ivm
01699  * \param context
01700  * \param mailbox
01701  * 
01702  * \return The ast_vm_user structure for the user that was found.
01703  */
01704 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
01705 {
01706    /* This function could be made to generate one from a database, too */
01707    struct ast_vm_user *vmu = NULL, *cur;
01708    AST_LIST_LOCK(&users);
01709 
01710    if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
01711       context = "default";
01712 
01713    AST_LIST_TRAVERSE(&users, cur, list) {
01714 #ifdef IMAP_STORAGE
01715       if (cur->imapversion != imapversion) {
01716          continue;
01717       }
01718 #endif
01719       if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
01720          break;
01721       if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
01722          break;
01723    }
01724    if (cur) {
01725       /* Make a copy, so that on a reload, we have no race */
01726       if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
01727          *vmu = *cur;
01728          if (!ivm) {
01729             vmu->email = ast_strdup(cur->email);
01730             vmu->emailbody = ast_strdup(cur->emailbody);
01731             vmu->emailsubject = ast_strdup(cur->emailsubject);
01732          }
01733          ast_set2_flag(vmu, !ivm, VM_ALLOCED);
01734          AST_LIST_NEXT(vmu, list) = NULL;
01735       }
01736    } else
01737       vmu = find_user_realtime(ivm, context, mailbox);
01738    AST_LIST_UNLOCK(&users);
01739    return vmu;
01740 }
01741 
01742 /*!
01743  * \brief Resets a user password to a specified password.
01744  * \param context
01745  * \param mailbox
01746  * \param newpass
01747  *
01748  * This does the actual change password work, called by the vm_change_password() function.
01749  *
01750  * \return zero on success, -1 on error.
01751  */
01752 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
01753 {
01754    /* This function could be made to generate one from a database, too */
01755    struct ast_vm_user *cur;
01756    int res = -1;
01757    AST_LIST_LOCK(&users);
01758    AST_LIST_TRAVERSE(&users, cur, list) {
01759       if ((!context || !strcasecmp(context, cur->context)) &&
01760          (!strcasecmp(mailbox, cur->mailbox)))
01761             break;
01762    }
01763    if (cur) {
01764       ast_copy_string(cur->password, newpass, sizeof(cur->password));
01765       res = 0;
01766    }
01767    AST_LIST_UNLOCK(&users);
01768    return res;
01769 }
01770 
01771 /*!
01772  * \brief Check if configuration file is valid
01773  */
01774 static inline int valid_config(const struct ast_config *cfg)
01775 {
01776    return cfg && cfg != CONFIG_STATUS_FILEINVALID;
01777 }
01778 
01779 /*! 
01780  * \brief The handler for the change password option.
01781  * \param vmu The voicemail user to work with.
01782  * \param newpassword The new password (that has been gathered from the appropriate prompting).
01783  * This is called when a new user logs in for the first time and the option to force them to change their password is set.
01784  * It is also called when the user wants to change their password from menu option '5' on the mailbox options menu.
01785  */
01786 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
01787 {
01788    struct ast_config   *cfg = NULL;
01789    struct ast_variable *var = NULL;
01790    struct ast_category *cat = NULL;
01791    char *category = NULL, *value = NULL, *new = NULL;
01792    const char *tmp = NULL;
01793    struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
01794    char secretfn[PATH_MAX] = "";
01795    int found = 0;
01796 
01797    if (!change_password_realtime(vmu, newpassword))
01798       return;
01799 
01800    /* check if we should store the secret in the spool directory next to the messages */
01801    switch (vmu->passwordlocation) {
01802    case OPT_PWLOC_SPOOLDIR:
01803       snprintf(secretfn, sizeof(secretfn), "%s%s/%s/secret.conf", VM_SPOOL_DIR, vmu->context, vmu->mailbox);
01804       if (write_password_to_file(secretfn, newpassword) == 0) {
01805          ast_test_suite_event_notify("PASSWORDCHANGED", "Message: secret.conf updated with new password\r\nPasswordSource: secret.conf");
01806          ast_verb(4, "Writing voicemail password to file %s succeeded\n", secretfn);
01807          reset_user_pw(vmu->context, vmu->mailbox, newpassword);
01808          ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
01809          break;
01810       } else {
01811          ast_verb(4, "Writing voicemail password to file %s failed, falling back to config file\n", secretfn);
01812       }
01813       /* Fall-through */
01814    case OPT_PWLOC_VOICEMAILCONF:
01815       if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags)) && valid_config(cfg)) {
01816          while ((category = ast_category_browse(cfg, category))) {
01817             if (!strcasecmp(category, vmu->context)) {
01818                if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
01819                   ast_log(AST_LOG_WARNING, "We could not find the mailbox.\n");
01820                   break;
01821                }
01822                value = strstr(tmp, ",");
01823                if (!value) {
01824                   new = ast_alloca(strlen(newpassword)+1);
01825                   sprintf(new, "%s", newpassword);
01826                } else {
01827                   new = ast_alloca((strlen(value) + strlen(newpassword) + 1));
01828                   sprintf(new, "%s%s", newpassword, value);
01829                }
01830                if (!(cat = ast_category_get(cfg, category, NULL))) {
01831                   ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
01832                   break;
01833                }
01834                ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
01835                found = 1;
01836             }
01837          }
01838          /* save the results */
01839          if (found) {
01840             ast_test_suite_event_notify("PASSWORDCHANGED", "Message: voicemail.conf updated with new password\r\nPasswordSource: voicemail.conf");
01841             reset_user_pw(vmu->context, vmu->mailbox, newpassword);
01842             ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
01843             ast_config_text_file_save(VOICEMAIL_CONFIG, cfg, "app_voicemail");
01844             ast_config_destroy(cfg);
01845             break;
01846          }
01847 
01848          ast_config_destroy(cfg);
01849       }
01850       /* Fall-through */
01851    case OPT_PWLOC_USERSCONF:
01852       /* check users.conf and update the password stored for the mailbox */
01853       /* if no vmsecret entry exists create one. */
01854       if ((cfg = ast_config_load("users.conf", config_flags)) && valid_config(cfg)) {
01855          ast_debug(4, "we are looking for %s\n", vmu->mailbox);
01856          for (category = ast_category_browse(cfg, NULL); category; category = ast_category_browse(cfg, category)) {
01857             ast_debug(4, "users.conf: %s\n", category);
01858             if (!strcasecmp(category, vmu->mailbox)) {
01859                if (!ast_variable_retrieve(cfg, category, "vmsecret")) {
01860                   ast_debug(3, "looks like we need to make vmsecret!\n");
01861                   var = ast_variable_new("vmsecret", newpassword, "");
01862                } else {
01863                   var = NULL;
01864                }
01865                new = ast_alloca(strlen(newpassword) + 1);
01866                sprintf(new, "%s", newpassword);
01867                if (!(cat = ast_category_get(cfg, category, NULL))) {
01868                   ast_debug(4, "failed to get category!\n");
01869                   ast_free(var);
01870                   break;
01871                }
01872                if (!var) {
01873                   ast_variable_update(cat, "vmsecret", new, NULL, 0);
01874                } else {
01875                   ast_variable_append(cat, var);
01876                }
01877                found = 1;
01878                break;
01879             }
01880          }
01881          /* save the results and clean things up */
01882          if (found) {
01883             ast_test_suite_event_notify("PASSWORDCHANGED", "Message: users.conf updated with new password\r\nPasswordSource: users.conf");
01884             reset_user_pw(vmu->context, vmu->mailbox, newpassword);
01885             ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
01886             ast_config_text_file_save("users.conf", cfg, "app_voicemail");
01887          }
01888 
01889          ast_config_destroy(cfg);
01890       }
01891    }
01892 }
01893 
01894 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
01895 {
01896    char buf[255];
01897    snprintf(buf, sizeof(buf), "%s %s %s %s", ext_pass_cmd, vmu->context, vmu->mailbox, newpassword);
01898    ast_debug(1, "External password: %s\n",buf);
01899    if (!ast_safe_system(buf)) {
01900       ast_test_suite_event_notify("PASSWORDCHANGED", "Message: external script updated with new password\r\nPasswordSource: external");
01901       ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
01902       /* Reset the password in memory, too */
01903       reset_user_pw(vmu->context, vmu->mailbox, newpassword);
01904    }
01905 }
01906 
01907 /*! 
01908  * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
01909  * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
01910  * \param len The length of the path string that was written out.
01911  * \param context
01912  * \param ext 
01913  * \param folder 
01914  * 
01915  * The path is constructed as 
01916  *    VM_SPOOL_DIRcontext/ext/folder
01917  *
01918  * \return zero on success, -1 on error.
01919  */
01920 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
01921 {
01922    return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
01923 }
01924 
01925 /*! 
01926  * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
01927  * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
01928  * \param len The length of the path string that was written out.
01929  * \param dir 
01930  * \param num 
01931  * 
01932  * The path is constructed as 
01933  *    VM_SPOOL_DIRcontext/ext/folder
01934  *
01935  * \return zero on success, -1 on error.
01936  */
01937 static int make_file(char *dest, const int len, const char *dir, const int num)
01938 {
01939    return snprintf(dest, len, "%s/msg%04d", dir, num);
01940 }
01941 
01942 /* same as mkstemp, but return a FILE * */
01943 static FILE *vm_mkftemp(char *template)
01944 {
01945    FILE *p = NULL;
01946    int pfd = mkstemp(template);
01947    chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
01948    if (pfd > -1) {
01949       p = fdopen(pfd, "w+");
01950       if (!p) {
01951          close(pfd);
01952          pfd = -1;
01953       }
01954    }
01955    return p;
01956 }
01957 
01958 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
01959  * \param dest    String. base directory.
01960  * \param len     Length of dest.
01961  * \param context String. Ignored if is null or empty string.
01962  * \param ext     String. Ignored if is null or empty string.
01963  * \param folder  String. Ignored if is null or empty string. 
01964  * \return -1 on failure, 0 on success.
01965  */
01966 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
01967 {
01968    mode_t   mode = VOICEMAIL_DIR_MODE;
01969    int res;
01970 
01971    make_dir(dest, len, context, ext, folder);
01972    if ((res = ast_mkdir(dest, mode))) {
01973       ast_log(AST_LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
01974       return -1;
01975    }
01976    return 0;
01977 }
01978 
01979 static const char *mbox(struct ast_vm_user *vmu, int id)
01980 {
01981 #ifdef IMAP_STORAGE
01982    if (vmu && id == 0) {
01983       return vmu->imapfolder;
01984    }
01985 #endif
01986    return (id >= 0 && id < ARRAY_LEN(mailbox_folders)) ? mailbox_folders[id] : "Unknown";
01987 }
01988 
01989 static const char *vm_index_to_foldername(int id)
01990 {
01991    return mbox(NULL, id);
01992 }
01993 
01994 
01995 static int get_folder_by_name(const char *name)
01996 {
01997    size_t i;
01998 
01999    for (i = 0; i < ARRAY_LEN(mailbox_folders); i++) {
02000       if (strcasecmp(name, mailbox_folders[i]) == 0) {
02001          return i;
02002       }
02003    }
02004 
02005    return -1;
02006 }
02007 
02008 static void free_user(struct ast_vm_user *vmu)
02009 {
02010    if (ast_test_flag(vmu, VM_ALLOCED)) {
02011 
02012       ast_free(vmu->email);
02013       vmu->email = NULL;
02014 
02015       ast_free(vmu->emailbody);
02016       vmu->emailbody = NULL;
02017 
02018       ast_free(vmu->emailsubject);
02019       vmu->emailsubject = NULL;
02020 
02021       ast_free(vmu);
02022    }
02023 }
02024 
02025 static int vm_allocate_dh(struct vm_state *vms, struct ast_vm_user *vmu, int count_msg) {
02026 
02027    int arraysize = (vmu->maxmsg > count_msg ? vmu->maxmsg : count_msg);
02028 
02029    /* remove old allocation */
02030    if (vms->deleted) {
02031       ast_free(vms->deleted);
02032       vms->deleted = NULL;
02033    }
02034    if (vms->heard) {
02035       ast_free(vms->heard);
02036       vms->heard = NULL;
02037    }
02038    vms->dh_arraysize = 0;
02039 
02040    if (arraysize > 0) {
02041       if (!(vms->deleted = ast_calloc(arraysize, sizeof(int)))) {
02042          return -1;
02043       }
02044       if (!(vms->heard = ast_calloc(arraysize, sizeof(int)))) {
02045          ast_free(vms->deleted);
02046          vms->deleted = NULL;
02047          return -1;
02048       }
02049       vms->dh_arraysize = arraysize;
02050    }
02051 
02052    return 0;
02053 }
02054 
02055 /* All IMAP-specific functions should go in this block. This
02056  * keeps them from being spread out all over the code */
02057 #ifdef IMAP_STORAGE
02058 static void vm_imap_delete(char *file, int msgnum, struct ast_vm_user *vmu)
02059 {
02060    char arg[10];
02061    struct vm_state *vms;
02062    unsigned long messageNum;
02063 
02064    /* If greetings aren't stored in IMAP, just delete the file */
02065    if (msgnum < 0 && !imapgreetings) {
02066       ast_filedelete(file, NULL);
02067       return;
02068    }
02069 
02070    if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
02071       ast_log(LOG_WARNING, "Couldn't find a vm_state for mailbox %s. Unable to set \\DELETED flag for message %d\n", vmu->mailbox, msgnum);
02072       return;
02073    }
02074 
02075    if (msgnum < 0) {
02076       imap_delete_old_greeting(file, vms);
02077       return;
02078    }
02079 
02080    /* find real message number based on msgnum */
02081    /* this may be an index into vms->msgArray based on the msgnum. */
02082    messageNum = vms->msgArray[msgnum];
02083    if (messageNum == 0) {
02084       ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n", msgnum, messageNum);
02085       return;
02086    }
02087    ast_debug(3, "deleting msgnum %d, which is mailbox message %lu\n", msgnum, messageNum);
02088    /* delete message */
02089    snprintf (arg, sizeof(arg), "%lu", messageNum);
02090    ast_mutex_lock(&vms->lock);
02091    mail_setflag (vms->mailstream, arg, "\\DELETED");
02092    mail_expunge(vms->mailstream);
02093    ast_mutex_unlock(&vms->lock);
02094 }
02095 
02096 static void vm_imap_update_msg_id(char *dir, int msgnum, const char *msg_id, struct ast_vm_user *vmu, struct ast_config *msg_cfg, int folder)
02097 {
02098    struct ast_channel *chan;
02099    char *cid;
02100    char *cid_name;
02101    char *cid_num;
02102    struct vm_state *vms;
02103    const char *duration_str;
02104    int duration = 0;
02105 
02106    /*
02107     * First, get things initially set up. If any of this fails, then
02108     * back out before doing anything substantial
02109     */
02110    vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0);
02111    if (!vms) {
02112       return;
02113    }
02114 
02115    if (open_mailbox(vms, vmu, folder)) {
02116       return;
02117    }
02118 
02119    chan = ast_dummy_channel_alloc();
02120    if (!chan) {
02121       close_mailbox(vms, vmu);
02122       return;
02123    }
02124 
02125    /*
02126     * We need to make sure the new message we save has the same
02127     * callerid, flag, and duration as the original message
02128     */
02129    cid = ast_strdupa(ast_variable_retrieve(msg_cfg, "message", "callerid"));
02130 
02131    if (!ast_strlen_zero(cid)) {
02132       ast_callerid_parse(cid, &cid_name, &cid_num);
02133       ast_party_caller_init(ast_channel_caller(chan));
02134       if (!ast_strlen_zero(cid_name)) {
02135          ast_channel_caller(chan)->id.name.valid = 1;
02136          ast_channel_caller(chan)->id.name.str = ast_strdup(cid_name);
02137       }
02138       if (!ast_strlen_zero(cid_num)) {
02139          ast_channel_caller(chan)->id.number.valid = 1;
02140          ast_channel_caller(chan)->id.number.str = ast_strdup(cid_num);
02141       }
02142    }
02143 
02144    duration_str = ast_variable_retrieve(msg_cfg, "message", "duration");
02145 
02146    if (!ast_strlen_zero(duration_str)) {
02147       sscanf(duration_str, "%30d", &duration);
02148    }
02149 
02150    /*
02151     * IMAP messages cannot be altered once delivered. So we have to delete the
02152     * current message and then re-add it with the updated message ID.
02153     *
02154     * Furthermore, there currently is no atomic way to create a new message and to
02155     * store it in an arbitrary folder. So we have to save it to the INBOX and then
02156     * move to the appropriate folder.
02157     */
02158    if (!imap_store_file(dir, vmu->mailbox, vmu->context, msgnum, chan, vmu, vmfmts,
02159          duration, vms, ast_variable_retrieve(msg_cfg, "message", "flag"), msg_id)) {
02160       if (folder != NEW_FOLDER) {
02161          save_to_folder(vmu, vms, msgnum, folder, NULL, 1);
02162       }
02163       vm_imap_delete(dir, msgnum, vmu);
02164    }
02165    close_mailbox(vms, vmu);
02166    ast_channel_unref(chan);
02167 }
02168 
02169 static int imap_retrieve_greeting(const char *dir, const int msgnum, struct ast_vm_user *vmu)
02170 {
02171    struct vm_state *vms_p;
02172    char *file, *filename;
02173    char *attachment;
02174    int i;
02175    BODY *body;
02176    int ret = 0;
02177    int curr_mbox;
02178 
02179    /* This function is only used for retrieval of IMAP greetings
02180     * regular messages are not retrieved this way, nor are greetings
02181     * if they are stored locally*/
02182    if (msgnum > -1 || !imapgreetings) {
02183       return 0;
02184    } else {
02185       file = strrchr(ast_strdupa(dir), '/');
02186       if (file)
02187          *file++ = '\0';
02188       else {
02189          ast_debug(1, "Failed to procure file name from directory passed.\n");
02190          return -1;
02191       }
02192    }
02193 
02194    /* check if someone is accessing this box right now... */
02195    if (!(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && 
02196       !(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
02197       /* Unlike when retrieving a message, it is reasonable not to be able to find a 
02198       * vm_state for a mailbox when trying to retrieve a greeting. Just create one,
02199       * that's all we need to do.
02200       */
02201       if (!(vms_p = create_vm_state_from_user(vmu))) {
02202          ast_log(LOG_NOTICE, "Unable to create vm_state object!\n");
02203          return -1;
02204       }
02205    }
02206 
02207    /* Greetings will never have a prepended message */
02208    *vms_p->introfn = '\0';
02209 
02210    ast_mutex_lock(&vms_p->lock);
02211 
02212    /* get the current mailbox so that we can point the mailstream back to it later */
02213    curr_mbox = get_folder_by_name(vms_p->curbox);
02214 
02215    if (init_mailstream(vms_p, GREETINGS_FOLDER) || !vms_p->mailstream) {
02216       ast_log(AST_LOG_ERROR, "IMAP mailstream is NULL or can't init_mailstream\n");
02217       ast_mutex_unlock(&vms_p->lock);
02218       return -1;
02219    }
02220 
02221    /*XXX Yuck, this could probably be done a lot better */
02222    for (i = 0; i < vms_p->mailstream->nmsgs; i++) {
02223       mail_fetchstructure(vms_p->mailstream, i + 1, &body);
02224       /* We have the body, now we extract the file name of the first attachment. */
02225       if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
02226          attachment = ast_strdupa(body->nested.part->next->body.parameter->value);
02227       } else {
02228          ast_log(AST_LOG_ERROR, "There is no file attached to this IMAP message.\n");
02229          ret = -1;
02230          break;
02231       }
02232       filename = strsep(&attachment, ".");
02233       if (!strcmp(filename, file)) {
02234          ast_copy_string(vms_p->fn, dir, sizeof(vms_p->fn));
02235          vms_p->msgArray[vms_p->curmsg] = i + 1;
02236          save_body(body, vms_p, "2", attachment, 0);
02237          ret = 0;
02238          break;
02239       }
02240    }
02241 
02242    if (curr_mbox != -1) {
02243       /* restore previous mbox stream */
02244       if (init_mailstream(vms_p, curr_mbox) || !vms_p->mailstream) {
02245          ast_log(AST_LOG_ERROR, "IMAP mailstream is NULL or can't init_mailstream\n");
02246          ret = -1;
02247       }
02248    }
02249    ast_mutex_unlock(&vms_p->lock);
02250    return ret;
02251 }
02252 
02253 static int imap_retrieve_file(const char *dir, const int msgnum, const char *mailbox, const char *context)
02254 {
02255    BODY *body;
02256    char *header_content;
02257    char *attachedfilefmt;
02258    char buf[80];
02259    struct vm_state *vms;
02260    char text_file[PATH_MAX];
02261    FILE *text_file_ptr;
02262    int res = 0;
02263    struct ast_vm_user *vmu;
02264    int curr_mbox;
02265 
02266    if (!(vmu = find_user(NULL, context, mailbox))) {
02267       ast_log(LOG_WARNING, "Couldn't find user with mailbox %s@%s\n", mailbox, context);
02268       return -1;
02269    }
02270 
02271    if (msgnum < 0) {
02272       if (imapgreetings) {
02273          res = imap_retrieve_greeting(dir, msgnum, vmu);
02274          goto exit;
02275       } else {
02276          res = 0;
02277          goto exit;
02278       }
02279    }
02280 
02281    /* Before anything can happen, we need a vm_state so that we can
02282     * actually access the imap server through the vms->mailstream
02283     */
02284    if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
02285       /* This should not happen. If it does, then I guess we'd
02286        * need to create the vm_state, extract which mailbox to
02287        * open, and then set up the msgArray so that the correct
02288        * IMAP message could be accessed. If I have seen correctly
02289        * though, the vms should be obtainable from the vmstates list
02290        * and should have its msgArray properly set up.
02291        */
02292       ast_log(LOG_ERROR, "Couldn't find a vm_state for mailbox %s!!! Oh no!\n", vmu->mailbox);
02293       res = -1;
02294       goto exit;
02295    }
02296 
02297    /* Ensure we have the correct mailbox open and have a valid mailstream for it */
02298    curr_mbox = get_folder_by_name(vms->curbox);
02299    if (curr_mbox < 0) {
02300       ast_debug(3, "Mailbox folder curbox not set, defaulting to Inbox\n");
02301       curr_mbox = 0;
02302    }
02303    init_mailstream(vms, curr_mbox);
02304    if (!vms->mailstream) {
02305       ast_log(AST_LOG_ERROR, "IMAP mailstream for %s is NULL\n", vmu->mailbox);
02306       res = -1;
02307       goto exit;
02308    }
02309 
02310    make_file(vms->fn, sizeof(vms->fn), dir, msgnum);
02311    snprintf(vms->introfn, sizeof(vms->introfn), "%sintro", vms->fn);
02312 
02313    /* Don't try to retrieve a message from IMAP if it already is on the file system */
02314    if (ast_fileexists(vms->fn, NULL, NULL) > 0) {
02315       res = 0;
02316       goto exit;
02317    }
02318 
02319    ast_debug(3, "Before mail_fetchheaders, curmsg is: %d, imap messages is %lu\n", msgnum, vms->msgArray[msgnum]);
02320    if (vms->msgArray[msgnum] == 0) {
02321       ast_log(LOG_WARNING, "Trying to access unknown message\n");
02322       res = -1;
02323       goto exit;
02324    }
02325 
02326    /* This will only work for new messages... */
02327    ast_mutex_lock(&vms->lock);
02328    header_content = mail_fetchheader (vms->mailstream, vms->msgArray[msgnum]);
02329    ast_mutex_unlock(&vms->lock);
02330    /* empty string means no valid header */
02331    if (ast_strlen_zero(header_content)) {
02332       ast_log(LOG_ERROR, "Could not fetch header for message number %ld\n", vms->msgArray[msgnum]);
02333       res = -1;
02334       goto exit;
02335    }
02336 
02337    ast_mutex_lock(&vms->lock);
02338    mail_fetchstructure(vms->mailstream, vms->msgArray[msgnum], &body);
02339    ast_mutex_unlock(&vms->lock);
02340 
02341    /* We have the body, now we extract the file name of the first attachment. */
02342    if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
02343       attachedfilefmt = ast_strdupa(body->nested.part->next->body.parameter->value);
02344    } else {
02345       ast_log(LOG_ERROR, "There is no file attached to this IMAP message.\n");
02346       res = -1;
02347       goto exit;
02348    }
02349    
02350    /* Find the format of the attached file */
02351 
02352    strsep(&attachedfilefmt, ".");
02353    if (!attachedfilefmt) {
02354       ast_log(LOG_ERROR, "File format could not be obtained from IMAP message attachment\n");
02355       res = -1;
02356       goto exit;
02357    }
02358    
02359    save_body(body, vms, "2", attachedfilefmt, 0);
02360    if (save_body(body, vms, "3", attachedfilefmt, 1)) {
02361       *vms->introfn = '\0';
02362    }
02363 
02364    /* Get info from headers!! */
02365    snprintf(text_file, sizeof(text_file), "%s.%s", vms->fn, "txt");
02366 
02367    if (!(text_file_ptr = fopen(text_file, "w"))) {
02368       ast_log(LOG_WARNING, "Unable to open/create file %s: %s\n", text_file, strerror(errno));
02369    }
02370 
02371    fprintf(text_file_ptr, "%s\n", "[message]");
02372 
02373    if (get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf))) {
02374       fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, ""));
02375    }
02376    if (get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf))) {
02377       fprintf(text_file_ptr, "<%s>\n", S_OR(buf, ""));
02378    }
02379    if (get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf))) {
02380       fprintf(text_file_ptr, "context=%s\n", S_OR(buf, ""));
02381    }
02382    if (get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf))) {
02383       fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, ""));
02384    }
02385    if (get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf))) {
02386       fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, ""));
02387    }
02388    if (get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf))) {
02389       fprintf(text_file_ptr, "category=%s\n", S_OR(buf, ""));
02390    }
02391    if (get_header_by_tag(header_content, "X-Asterisk-VM-Flag:", buf, sizeof(buf))) {
02392       fprintf(text_file_ptr, "flag=%s\n", S_OR(buf, ""));
02393    }
02394    if (get_header_by_tag(header_content, "X-Asterisk-VM-Message-ID:", buf, sizeof(buf))) {
02395       fprintf(text_file_ptr, "msg_id=%s\n", S_OR(buf, ""));
02396    }
02397    fclose(text_file_ptr);
02398 
02399 exit:
02400    free_user(vmu);
02401    return res;
02402 }
02403 
02404 static int folder_int(const char *folder)
02405 {
02406    /*assume a NULL folder means INBOX*/
02407    if (!folder) {
02408       return 0;
02409    }
02410    if (!strcasecmp(folder, imapfolder)) {
02411       return 0;
02412    } else if (!strcasecmp(folder, "Old")) {
02413       return 1;
02414    } else if (!strcasecmp(folder, "Work")) {
02415       return 2;
02416    } else if (!strcasecmp(folder, "Family")) {
02417       return 3;
02418    } else if (!strcasecmp(folder, "Friends")) {
02419       return 4;
02420    } else if (!strcasecmp(folder, "Cust1")) {
02421       return 5;
02422    } else if (!strcasecmp(folder, "Cust2")) {
02423       return 6;
02424    } else if (!strcasecmp(folder, "Cust3")) {
02425       return 7;
02426    } else if (!strcasecmp(folder, "Cust4")) {
02427       return 8;
02428    } else if (!strcasecmp(folder, "Cust5")) {
02429       return 9;
02430    } else if (!strcasecmp(folder, "Urgent")) {
02431       return 11;
02432    } else { /*assume they meant INBOX if folder is not found otherwise*/
02433       return 0;
02434    }
02435 }
02436 
02437 static int __messagecount(const char *context, const char *mailbox, const char *folder)
02438 {
02439    SEARCHPGM *pgm;
02440    SEARCHHEADER *hdr;
02441 
02442    struct ast_vm_user *vmu, vmus;
02443    struct vm_state *vms_p;
02444    int ret = 0;
02445    int fold = folder_int(folder);
02446    int urgent = 0;
02447    
02448    /* If URGENT, then look at INBOX */
02449    if (fold == 11) {
02450       fold = NEW_FOLDER;
02451       urgent = 1;
02452    }
02453 
02454    if (ast_strlen_zero(mailbox))
02455       return 0;
02456 
02457    /* We have to get the user before we can open the stream! */
02458    vmu = find_user(&vmus, context, mailbox);
02459    if (!vmu) {
02460       ast_log(AST_LOG_WARNING, "Couldn't find mailbox %s in context %s\n", mailbox, context);
02461       return -1;
02462    } else {
02463       /* No IMAP account available */
02464       if (vmu->imapuser[0] == '\0') {
02465          ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
02466          return -1;
02467       }
02468    }
02469 
02470    /* No IMAP account available */
02471    if (vmu->imapuser[0] == '\0') {
02472       ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
02473       free_user(vmu);
02474       return -1;
02475    }
02476 
02477    /* check if someone is accessing this box right now... */
02478    vms_p = get_vm_state_by_imapuser(vmu->imapuser, 1);
02479    if (!vms_p) {
02480       vms_p = get_vm_state_by_mailbox(mailbox, context, 1);
02481    }
02482    if (vms_p) {
02483       ast_debug(3, "Returning before search - user is logged in\n");
02484       if (fold == 0) { /* INBOX */
02485          return urgent ? vms_p->urgentmessages : vms_p->newmessages;
02486       }
02487       if (fold == 1) { /* Old messages */
02488          return vms_p->oldmessages;
02489       }
02490    }
02491 
02492    /* add one if not there... */
02493    vms_p = get_vm_state_by_imapuser(vmu->imapuser, 0);
02494    if (!vms_p) {
02495       vms_p = get_vm_state_by_mailbox(mailbox, context, 0);
02496    }
02497 
02498    if (!vms_p) {
02499       vms_p = create_vm_state_from_user(vmu);
02500    }
02501    ret = init_mailstream(vms_p, fold);
02502    if (!vms_p->mailstream) {
02503       ast_log(AST_LOG_ERROR, "Houston we have a problem - IMAP mailstream is NULL\n");
02504       return -1;
02505    }
02506    if (ret == 0) {
02507       ast_mutex_lock(&vms_p->lock);
02508       pgm = mail_newsearchpgm ();
02509       hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)(!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : mailbox));
02510       hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", (char *) S_OR(context, "default"));
02511       pgm->header = hdr;
02512       if (fold != OLD_FOLDER) {
02513          pgm->unseen = 1;
02514          pgm->seen = 0;
02515       }
02516       /* In the special case where fold is 1 (old messages) we have to do things a bit
02517        * differently. Old messages are stored in the INBOX but are marked as "seen"
02518        */
02519       else {
02520          pgm->unseen = 0;
02521          pgm->seen = 1;
02522       }
02523       /* look for urgent messages */
02524       if (fold == NEW_FOLDER) {
02525          if (urgent) {
02526             pgm->flagged = 1;
02527             pgm->unflagged = 0;
02528          } else {
02529             pgm->flagged = 0;
02530             pgm->unflagged = 1;
02531          }
02532       }
02533       pgm->undeleted = 1;
02534       pgm->deleted = 0;
02535 
02536       vms_p->vmArrayIndex = 0;
02537       mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
02538       if (fold == 0 && urgent == 0)
02539          vms_p->newmessages = vms_p->vmArrayIndex;
02540       if (fold == 1)
02541          vms_p->oldmessages = vms_p->vmArrayIndex;
02542       if (fold == 0 && urgent == 1)
02543          vms_p->urgentmessages = vms_p->vmArrayIndex;
02544       /*Freeing the searchpgm also frees the searchhdr*/
02545       mail_free_searchpgm(&pgm);
02546       ast_mutex_unlock(&vms_p->lock);
02547       vms_p->updated = 0;
02548       return vms_p->vmArrayIndex;
02549    } else {
02550       ast_mutex_lock(&vms_p->lock);
02551       mail_ping(vms_p->mailstream);
02552       ast_mutex_unlock(&vms_p->lock);
02553    }
02554    return 0;
02555 }
02556 
02557 static int imap_check_limits(struct ast_channel *chan, struct vm_state *vms, struct ast_vm_user *vmu, int msgnum)
02558 {
02559    /* Check if mailbox is full */
02560    check_quota(vms, vmu->imapfolder);
02561    if (vms->quota_limit && vms->quota_usage >= vms->quota_limit) {
02562       ast_debug(1, "*** QUOTA EXCEEDED!! %u >= %u\n", vms->quota_usage, vms->quota_limit);
02563       if (chan) {
02564          ast_play_and_wait(chan, "vm-mailboxfull");
02565       }
02566       return -1;
02567    }
02568 
02569    /* Check if we have exceeded maxmsg */
02570    ast_debug(3, "Checking message number quota: mailbox has %d messages, maximum is set to %d, current messages %d\n", msgnum, vmu->maxmsg, inprocess_count(vmu->mailbox, vmu->context, 0));
02571    if (msgnum >= vmu->maxmsg - inprocess_count(vmu->mailbox, vmu->context, +1)) {
02572       ast_log(LOG_WARNING, "Unable to leave message since we will exceed the maximum number of messages allowed (%u >= %u)\n", msgnum, vmu->maxmsg);
02573       if (chan) {
02574          ast_play_and_wait(chan, "vm-mailboxfull");
02575          pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
02576       }
02577       return -1;
02578    }
02579 
02580    return 0;
02581 }
02582 
02583 /*!
02584  * \brief Gets the number of messages that exist in a mailbox folder.
02585  * \param mailbox_id
02586  * \param folder
02587  * 
02588  * This method is used when IMAP backend is used.
02589  * \return The number of messages in this mailbox folder (zero or more).
02590  */
02591 static int messagecount(const char *mailbox_id, const char *folder)
02592 {
02593    char *context;
02594    char *mailbox;
02595 
02596    if (ast_strlen_zero(mailbox_id)
02597       || separate_mailbox(ast_strdupa(mailbox_id), &mailbox, &context)) {
02598       return 0;
02599    }
02600 
02601    if (ast_strlen_zero(folder) || !strcmp(folder, "INBOX")) {
02602       return __messagecount(context, mailbox, "INBOX") + __messagecount(context, mailbox, "Urgent");
02603    } else {
02604       return __messagecount(context, mailbox, folder);
02605    }
02606 }
02607 
02608 static int imap_store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag, const char *msg_id)
02609 {
02610    char *myserveremail = serveremail;
02611    char fn[PATH_MAX];
02612    char introfn[PATH_MAX];
02613    char mailbox[256];
02614    char *stringp;
02615    FILE *p = NULL;
02616    char tmp[80] = "/tmp/astmail-XXXXXX";
02617    long len;
02618    void *buf;
02619    int tempcopy = 0;
02620    STRING str;
02621    int ret; /* for better error checking */
02622    char *imap_flags = NIL;
02623    int msgcount;
02624    int box = NEW_FOLDER;
02625 
02626    snprintf(mailbox, sizeof(mailbox), "%s@%s", vmu->mailbox, vmu->context);
02627    msgcount = messagecount(mailbox, "INBOX") + messagecount(mailbox, "Old");
02628 
02629    /* Back out early if this is a greeting and we don't want to store greetings in IMAP */
02630    if (msgnum < 0) {
02631       if(!imapgreetings) {
02632          return 0;
02633       } else {
02634          box = GREETINGS_FOLDER;
02635       }
02636    }
02637 
02638    if (imap_check_limits(chan, vms, vmu, msgcount)) {
02639       return -1;
02640    }
02641 
02642    /* Set urgent flag for IMAP message */
02643    if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) {
02644       ast_debug(3, "Setting message flag \\\\FLAGGED.\n");
02645       imap_flags = "\\FLAGGED";
02646    }
02647 
02648    /* Attach only the first format */
02649    fmt = ast_strdupa(fmt);
02650    stringp = fmt;
02651    strsep(&stringp, "|");
02652 
02653    if (!ast_strlen_zero(vmu->serveremail))
02654       myserveremail = vmu->serveremail;
02655 
02656    if (msgnum > -1)
02657       make_file(fn, sizeof(fn), dir, msgnum);
02658    else
02659       ast_copy_string (fn, dir, sizeof(fn));
02660 
02661    snprintf(introfn, sizeof(introfn), "%sintro", fn);
02662    if (ast_fileexists(introfn, NULL, NULL) <= 0) {
02663       *introfn = '\0';
02664    }
02665 
02666    if (ast_strlen_zero(vmu->email)) {
02667       /* We need the vmu->email to be set when we call make_email_file, but
02668        * if we keep it set, a duplicate e-mail will be created. So at the end
02669        * of this function, we will revert back to an empty string if tempcopy
02670        * is 1.
02671        */
02672       vmu->email = ast_strdup(vmu->imapuser);
02673       tempcopy = 1;
02674    }
02675 
02676    if (!strcmp(fmt, "wav49"))
02677       fmt = "WAV";
02678    ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
02679 
02680    /* Make a temporary file instead of piping directly to sendmail, in case the mail
02681       command hangs. */
02682    if (!(p = vm_mkftemp(tmp))) {
02683       ast_log(AST_LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
02684       if (tempcopy) {
02685          ast_free(vmu->email);
02686          vmu->email = NULL;
02687       }
02688       return -1;
02689    }
02690 
02691    if (msgnum < 0 && imapgreetings) {
02692       if ((ret = init_mailstream(vms, GREETINGS_FOLDER))) {
02693          ast_log(AST_LOG_WARNING, "Unable to open mailstream.\n");
02694          return -1;
02695       }
02696       imap_delete_old_greeting(fn, vms);
02697    }
02698 
02699    make_email_file(p, myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, "INBOX",
02700       S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
02701       S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL),
02702       fn, introfn, fmt, duration, 1, chan, NULL, 1, flag, msg_id);
02703    /* read mail file to memory */
02704    len = ftell(p);
02705    rewind(p);
02706    if (!(buf = ast_malloc(len + 1))) {
02707       ast_log(AST_LOG_ERROR, "Can't allocate %ld bytes to read message\n", len + 1);
02708       fclose(p);
02709       if (tempcopy)
02710          *(vmu->email) = '\0';
02711       return -1;
02712    }
02713    if (fread(buf, len, 1, p) < len) {
02714       if (ferror(p)) {
02715          ast_log(LOG_ERROR, "Short read while reading in mail file.\n");
02716          return -1;
02717       }
02718    }
02719    ((char *) buf)[len] = '\0';
02720    INIT(&str, mail_string, buf, len);
02721    ret = init_mailstream(vms, box);
02722    if (ret == 0) {
02723       imap_mailbox_name(mailbox, sizeof(mailbox), vms, box, 1);
02724       ast_mutex_lock(&vms->lock);
02725       if(!mail_append_full(vms->mailstream, mailbox, imap_flags, NIL, &str))
02726          ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
02727       ast_mutex_unlock(&vms->lock);
02728       fclose(p);
02729       unlink(tmp);
02730       ast_free(buf);
02731    } else {
02732       ast_log(LOG_ERROR, "Could not initialize mailstream for %s\n", mailbox);
02733       fclose(p);
02734       unlink(tmp);
02735       ast_free(buf);
02736       return -1;
02737    }
02738    ast_debug(3, "%s stored\n", fn);
02739 
02740    if (tempcopy)
02741       *(vmu->email) = '\0';
02742    inprocess_count(vmu->mailbox, vmu->context, -1);
02743    return 0;
02744 
02745 }
02746 
02747 /*!
02748  * \brief Gets the number of messages that exist in the inbox folder.
02749  * \param mailbox_context
02750  * \param newmsgs The variable that is updated with the count of new messages within this inbox.
02751  * \param oldmsgs The variable that is updated with the count of old messages within this inbox.
02752  * \param urgentmsgs The variable that is updated with the count of urgent messages within this inbox.
02753  * 
02754  * This method is used when IMAP backend is used.
02755  * Simultaneously determines the count of new,old, and urgent messages. The total messages would then be the sum of these three.
02756  *
02757  * \return zero on success, -1 on error.
02758  */
02759 
02760 static int inboxcount2(const char *mailbox_context, int *urgentmsgs, int *newmsgs, int *oldmsgs)
02761 {
02762    char tmp[PATH_MAX] = "";
02763    char *mailboxnc;
02764    char *context;
02765    char *mb;
02766    char *cur;
02767    if (newmsgs)
02768       *newmsgs = 0;
02769    if (oldmsgs)
02770       *oldmsgs = 0;
02771    if (urgentmsgs)
02772       *urgentmsgs = 0;
02773 
02774    ast_debug(3, "Mailbox is set to %s\n", mailbox_context);
02775    /* If no mailbox, return immediately */
02776    if (ast_strlen_zero(mailbox_context))
02777       return 0;
02778 
02779    ast_copy_string(tmp, mailbox_context, sizeof(tmp));
02780    context = strchr(tmp, '@');
02781    if (strchr(mailbox_context, ',')) {
02782       int tmpnew, tmpold, tmpurgent;
02783       ast_copy_string(tmp, mailbox_context, sizeof(tmp));
02784       mb = tmp;
02785       while ((cur = strsep(&mb, ", "))) {
02786          if (!ast_strlen_zero(cur)) {
02787             if (inboxcount2(cur, urgentmsgs ? &tmpurgent : NULL, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
02788                return -1;
02789             else {
02790                if (newmsgs)
02791                   *newmsgs += tmpnew; 
02792                if (oldmsgs)
02793                   *oldmsgs += tmpold;
02794                if (urgentmsgs)
02795                   *urgentmsgs += tmpurgent;
02796             }
02797          }
02798       }
02799       return 0;
02800    }
02801    if (context) {
02802       *context = '\0';
02803       mailboxnc = tmp;
02804       context++;
02805    } else {
02806       context = "default";
02807       mailboxnc = (char *) mailbox_context;
02808    }
02809 
02810    if (newmsgs) {
02811       struct ast_vm_user *vmu = find_user(NULL, context, mailboxnc);
02812       if (!vmu) {
02813          ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailboxnc, context);
02814          return -1;
02815       }
02816       if ((*newmsgs = __messagecount(context, mailboxnc, vmu->imapfolder)) < 0) {
02817          free_user(vmu);
02818          return -1;
02819       }
02820       free_user(vmu);
02821    }
02822    if (oldmsgs) {
02823       if ((*oldmsgs = __messagecount(context, mailboxnc, "Old")) < 0) {
02824          return -1;
02825       }
02826    }
02827    if (urgentmsgs) {
02828       if ((*urgentmsgs = __messagecount(context, mailboxnc, "Urgent")) < 0) {
02829          return -1;
02830       }
02831    }
02832    return 0;
02833 }
02834 
02835 /** 
02836  * \brief Determines if the given folder has messages.
02837  * \param mailbox The @ delimited string for user@context. If no context is found, uses 'default' for the context.
02838  * \param folder the folder to look in
02839  *
02840  * This function is used when the mailbox is stored in an IMAP back end.
02841  * This invokes the messagecount(). Here we are interested in the presence of messages (> 0) only, not the actual count.
02842  * \return 1 if the folder has one or more messages. zero otherwise.
02843  */
02844 
02845 static int has_voicemail(const char *mailbox, const char *folder)
02846 {
02847    char tmp[256], *tmp2, *box, *context;
02848    ast_copy_string(tmp, mailbox, sizeof(tmp));
02849    tmp2 = tmp;
02850    if (strchr(tmp2, ',') || strchr(tmp2, '&')) {
02851       while ((box = strsep(&tmp2, ",&"))) {
02852          if (!ast_strlen_zero(box)) {
02853             if (has_voicemail(box, folder)) {
02854                return 1;
02855             }
02856          }
02857       }
02858    }
02859    if ((context = strchr(tmp, '@'))) {
02860       *context++ = '\0';
02861    } else {
02862       context = "default";
02863    }
02864    return __messagecount(context, tmp, folder) ? 1 : 0;
02865 }
02866 
02867 /*!
02868  * \brief Copies a message from one mailbox to another.
02869  * \param chan
02870  * \param vmu
02871  * \param imbox
02872  * \param msgnum
02873  * \param duration
02874  * \param recip
02875  * \param fmt
02876  * \param dir
02877  *
02878  * This works with IMAP storage based mailboxes.
02879  *
02880  * \return zero on success, -1 on error.
02881  */
02882 static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir, char *flag, const char *dest_folder)
02883 {
02884    struct vm_state *sendvms = NULL;
02885    char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
02886    if (msgnum >= recip->maxmsg) {
02887       ast_log(LOG_WARNING, "Unable to copy mail, mailbox %s is full\n", recip->mailbox);
02888       return -1;
02889    }
02890    if (!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 0))) {
02891       ast_log(LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
02892       return -1;
02893    }
02894    if (!get_vm_state_by_imapuser(recip->imapuser, 0)) {
02895       ast_log(LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
02896       return -1;
02897    }
02898    snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
02899    ast_mutex_lock(&sendvms->lock);
02900    if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(vmu, imbox)) == T)) {
02901       ast_mutex_unlock(&sendvms->lock);
02902       return 0;
02903    }
02904    ast_mutex_unlock(&sendvms->lock);
02905    ast_log(LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
02906    return -1;
02907 }
02908 
02909 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int use_folder)
02910 {
02911    char tmp[256], *t = tmp;
02912    size_t left = sizeof(tmp);
02913    
02914    if (box == OLD_FOLDER) {
02915       ast_copy_string(vms->curbox, mbox(NULL, NEW_FOLDER), sizeof(vms->curbox));
02916    } else {
02917       ast_copy_string(vms->curbox, mbox(NULL, box), sizeof(vms->curbox));
02918    }
02919 
02920    if (box == NEW_FOLDER) {
02921       ast_copy_string(vms->vmbox, "vm-INBOX", sizeof(vms->vmbox));
02922    } else {
02923       snprintf(vms->vmbox, sizeof(vms->vmbox), "vm-%s", mbox(NULL, box));
02924    }
02925 
02926    /* Build up server information */
02927    ast_build_string(&t, &left, "{%s:%s/imap", S_OR(vms->imapserver, imapserver), S_OR(vms->imapport, imapport));
02928 
02929    /* Add authentication user if present */
02930    if (!ast_strlen_zero(authuser))
02931       ast_build_string(&t, &left, "/authuser=%s", authuser);
02932 
02933    /* Add flags if present */
02934    if (!ast_strlen_zero(imapflags) || !(ast_strlen_zero(vms->imapflags))) {
02935       ast_build_string(&t, &left, "/%s", S_OR(vms->imapflags, imapflags));
02936    }
02937 
02938    /* End with username */
02939 #if 1
02940    ast_build_string(&t, &left, "/user=%s}", vms->imapuser);
02941 #else
02942    ast_build_string(&t, &left, "/user=%s/novalidate-cert}", vms->imapuser);
02943 #endif
02944    if (box == NEW_FOLDER || box == OLD_FOLDER)
02945       snprintf(spec, len, "%s%s", tmp, use_folder? vms->imapfolder: "INBOX");
02946    else if (box == GREETINGS_FOLDER)
02947       snprintf(spec, len, "%s%s", tmp, greetingfolder);
02948    else {   /* Other folders such as Friends, Family, etc... */
02949       if (!ast_strlen_zero(imapparentfolder)) {
02950          /* imapparentfolder would typically be set to INBOX */
02951          snprintf(spec, len, "%s%s%c%s", tmp, imapparentfolder, delimiter, mbox(NULL, box));
02952       } else {
02953          snprintf(spec, len, "%s%s", tmp, mbox(NULL, box));
02954       }
02955    }
02956 }
02957 
02958 static int init_mailstream(struct vm_state *vms, int box)
02959 {
02960    MAILSTREAM *stream = NIL;
02961    long debug;
02962    char tmp[256];
02963 
02964    if (!vms) {
02965       ast_log(LOG_ERROR, "vm_state is NULL!\n");
02966       return -1;
02967    }
02968    ast_debug(3, "vm_state user is:%s\n", vms->imapuser);
02969    if (vms->mailstream == NIL || !vms->mailstream) {
02970       ast_debug(1, "mailstream not set.\n");
02971    } else {
02972       stream = vms->mailstream;
02973    }
02974    /* debug = T;  user wants protocol telemetry? */
02975    debug = NIL;  /* NO protocol telemetry? */
02976 
02977    if (delimiter == '\0') {      /* did not probe the server yet */
02978       char *cp;
02979 #ifdef USE_SYSTEM_IMAP
02980 #include <imap/linkage.c>
02981 #elif defined(USE_SYSTEM_CCLIENT)
02982 #include <c-client/linkage.c>
02983 #else
02984 #include "linkage.c"
02985 #endif
02986       /* Connect to INBOX first to get folders delimiter */
02987       imap_mailbox_name(tmp, sizeof(tmp), vms, 0, 1);
02988       ast_mutex_lock(&vms->lock);
02989       ast_mutex_lock(&mail_open_lock);
02990       stream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
02991       ast_mutex_unlock(&mail_open_lock);
02992       ast_mutex_unlock(&vms->lock);
02993       if (stream == NIL) {
02994          ast_log(LOG_ERROR, "Can't connect to imap server %s\n", tmp);
02995          return -1;
02996       }
02997       get_mailbox_delimiter(vms, stream);
02998       /* update delimiter in imapfolder */
02999       for (cp = vms->imapfolder; *cp; cp++)
03000          if (*cp == '/')
03001             *cp = delimiter;
03002    }
03003    /* Now connect to the target folder */
03004    imap_mailbox_name(tmp, sizeof(tmp), vms, box, 1);
03005    ast_debug(3, "Before mail_open, server: %s, box:%d\n", tmp, box);
03006    ast_mutex_lock(&vms->lock);
03007    ast_mutex_lock(&mail_open_lock);
03008    vms->mailstream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
03009    ast_mutex_unlock(&mail_open_lock);
03010    ast_mutex_unlock(&vms->lock);
03011    if (vms->mailstream == NIL) {
03012       return -1;
03013    } else {
03014       return 0;
03015    }
03016 }
03017 
03018 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box)
03019 {
03020    SEARCHPGM *pgm;
03021    SEARCHHEADER *hdr;
03022    int urgent = 0;
03023 
03024    /* If Urgent, then look at INBOX */
03025    if (box == 11) {
03026       box = NEW_FOLDER;
03027       urgent = 1;
03028    }
03029 
03030    ast_copy_string(vms->imapuser, vmu->imapuser, sizeof(vms->imapuser));
03031    ast_copy_string(vms->imapfolder, vmu->imapfolder, sizeof(vms->imapfolder));
03032    ast_copy_string(vms->imapserver, vmu->imapserver, sizeof(vms->imapserver));
03033    ast_copy_string(vms->imapport, vmu->imapport, sizeof(vms->imapport));
03034    ast_copy_string(vms->imapflags, vmu->imapflags, sizeof(vms->imapflags));
03035    vms->imapversion = vmu->imapversion;
03036    ast_debug(3, "Before init_mailstream, user is %s\n", vmu->imapuser);
03037 
03038    if (init_mailstream(vms, box) || !vms->mailstream) {
03039       ast_log(AST_LOG_ERROR, "Could not initialize mailstream\n");
03040       return -1;
03041    }
03042 
03043    create_dirpath(vms->curdir, sizeof(vms->curdir), vmu->context, vms->username, vms->curbox);
03044 
03045    /* Check Quota */
03046    if  (box == 0)  {
03047       ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mbox(vmu, box));
03048       check_quota(vms, (char *) mbox(vmu, box));
03049    }
03050 
03051    ast_mutex_lock(&vms->lock);
03052    pgm = mail_newsearchpgm();
03053 
03054    /* Check IMAP folder for Asterisk messages only... */
03055    hdr = mail_newsearchheader("X-Asterisk-VM-Extension", (!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : vmu->mailbox));
03056    hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", vmu->context);
03057    pgm->header = hdr;
03058    pgm->deleted = 0;
03059    pgm->undeleted = 1;
03060 
03061    /* if box = NEW_FOLDER, check for new, if box = OLD_FOLDER, check for read */
03062    if (box == NEW_FOLDER && urgent == 1) {
03063       pgm->unseen = 1;
03064       pgm->seen = 0;
03065       pgm->flagged = 1;
03066       pgm->unflagged = 0;
03067    } else if (box == NEW_FOLDER && urgent == 0) {
03068       pgm->unseen = 1;
03069       pgm->seen = 0;
03070       pgm->flagged = 0;
03071       pgm->unflagged = 1;
03072    } else if (box == OLD_FOLDER) {
03073       pgm->seen = 1;
03074       pgm->unseen = 0;
03075    }
03076 
03077    ast_debug(3, "Before mail_search_full, user is %s\n", vmu->imapuser);
03078 
03079    vms->vmArrayIndex = 0;
03080    mail_search_full (vms->mailstream, NULL, pgm, NIL);
03081    vms->lastmsg = vms->vmArrayIndex - 1;
03082    mail_free_searchpgm(&pgm);
03083    /* Since IMAP storage actually stores both old and new messages in the same IMAP folder,
03084     * ensure to allocate enough space to account for all of them. Warn if old messages
03085     * have not been checked first as that is required.
03086     */
03087    if (box == 0 && !vms->dh_arraysize) {
03088       ast_log(LOG_WARNING, "The code expects the old messages to be checked first, fix the code.\n");
03089    }
03090    if (vm_allocate_dh(vms, vmu, box == 0 ? vms->vmArrayIndex + vms->oldmessages : vms->lastmsg)) {
03091       ast_mutex_unlock(&vms->lock);
03092       return -1;
03093    }
03094 
03095    ast_mutex_unlock(&vms->lock);
03096    return 0;
03097 }
03098 
03099 static void write_file(char *filename, char *buffer, unsigned long len)
03100 {
03101    FILE *output;
03102 
03103    output = fopen (filename, "w");
03104    if (fwrite(buffer, len, 1, output) != 1) {
03105       if (ferror(output)) {
03106          ast_log(LOG_ERROR, "Short write while writing e-mail body: %s.\n", strerror(errno));
03107       }
03108    }
03109    fclose (output);
03110 }
03111 
03112 static void update_messages_by_imapuser(const char *user, unsigned long number)
03113 {
03114    struct vm_state *vms = get_vm_state_by_imapuser(user, 1);
03115 
03116    if (!vms && !(vms = get_vm_state_by_imapuser(user, 0))) {
03117       return;
03118    }
03119 
03120    ast_debug(3, "saving mailbox message number %lu as message %d. Interactive set to %d\n", number, vms->vmArrayIndex, vms->interactive);
03121 
03122    /* Ensure we have room for the next message. */
03123    if (vms->vmArrayIndex >= vms->msg_array_max) {
03124       long *new_mem = ast_realloc(vms->msgArray, 2 * vms->msg_array_max * sizeof(long));
03125       if (!new_mem) {
03126          return;
03127       }
03128       vms->msgArray = new_mem;
03129       vms->msg_array_max *= 2;
03130    }
03131 
03132    vms->msgArray[vms->vmArrayIndex++] = number;
03133 }
03134 
03135 void mm_searched(MAILSTREAM *stream, unsigned long number)
03136 {
03137    char *mailbox = stream->mailbox, buf[1024] = "", *user;
03138 
03139    if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))))
03140       return;
03141 
03142    update_messages_by_imapuser(user, number);
03143 }
03144 
03145 static struct ast_vm_user *find_user_realtime_imapuser(const char *imapuser)
03146 {
03147    struct ast_variable *var;
03148    struct ast_vm_user *vmu;
03149 
03150    vmu = ast_calloc(1, sizeof *vmu);
03151    if (!vmu)
03152       return NULL;
03153 
03154    populate_defaults(vmu);
03155    ast_set_flag(vmu, VM_ALLOCED);
03156 
03157    var = ast_load_realtime("voicemail", "imapuser", imapuser, NULL);
03158    if (var) {
03159       apply_options_full(vmu, var);
03160       ast_variables_destroy(var);
03161       return vmu;
03162    } else {
03163       ast_free(vmu);
03164       return NULL;
03165    }
03166 }
03167 
03168 /* Interfaces to C-client */
03169 
03170 void mm_exists(MAILSTREAM * stream, unsigned long number)
03171 {
03172    /* mail_ping will callback here if new mail! */
03173    ast_debug(4, "Entering EXISTS callback for message %ld\n", number);
03174    if (number == 0) return;
03175    set_update(stream);
03176 }
03177 
03178 
03179 void mm_expunged(MAILSTREAM * stream, unsigned long number)
03180 {
03181    /* mail_ping will callback here if expunged mail! */
03182    ast_debug(4, "Entering EXPUNGE callback for message %ld\n", number);
03183    if (number == 0) return;
03184    set_update(stream);
03185 }
03186 
03187 
03188 void mm_flags(MAILSTREAM * stream, unsigned long number)
03189 {
03190    /* mail_ping will callback here if read mail! */
03191    ast_debug(4, "Entering FLAGS callback for message %ld\n", number);
03192    if (number == 0) return;
03193    set_update(stream);
03194 }
03195 
03196 
03197 void mm_notify(MAILSTREAM * stream, char *string, long errflg)
03198 {
03199    ast_debug(5, "Entering NOTIFY callback, errflag is %ld, string is %s\n", errflg, string);
03200    mm_log (string, errflg);
03201 }
03202 
03203 
03204 void mm_list(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
03205 {
03206    if (delimiter == '\0') {
03207       delimiter = delim;
03208    }
03209 
03210    ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
03211    if (attributes & LATT_NOINFERIORS)
03212       ast_debug(5, "no inferiors\n");
03213    if (attributes & LATT_NOSELECT)
03214       ast_debug(5, "no select\n");
03215    if (attributes & LATT_MARKED)
03216       ast_debug(5, "marked\n");
03217    if (attributes & LATT_UNMARKED)
03218       ast_debug(5, "unmarked\n");
03219 }
03220 
03221 
03222 void mm_lsub(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
03223 {
03224    ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
03225    if (attributes & LATT_NOINFERIORS)
03226       ast_debug(5, "no inferiors\n");
03227    if (attributes & LATT_NOSELECT)
03228       ast_debug(5, "no select\n");
03229    if (attributes & LATT_MARKED)
03230       ast_debug(5, "marked\n");
03231    if (attributes & LATT_UNMARKED)
03232       ast_debug(5, "unmarked\n");
03233 }
03234 
03235 
03236 void mm_status(MAILSTREAM * stream, char *mailbox, MAILSTATUS * status)
03237 {
03238    ast_log(AST_LOG_NOTICE, " Mailbox %s", mailbox);
03239    if (status->flags & SA_MESSAGES)
03240       ast_log(AST_LOG_NOTICE, ", %lu messages", status->messages);
03241    if (status->flags & SA_RECENT)
03242       ast_log(AST_LOG_NOTICE, ", %lu recent", status->recent);
03243    if (status->flags & SA_UNSEEN)
03244       ast_log(AST_LOG_NOTICE, ", %lu unseen", status->unseen);
03245    if (status->flags & SA_UIDVALIDITY)
03246       ast_log(AST_LOG_NOTICE, ", %lu UID validity", status->uidvalidity);
03247    if (status->flags & SA_UIDNEXT)
03248       ast_log(AST_LOG_NOTICE, ", %lu next UID", status->uidnext);
03249    ast_log(AST_LOG_NOTICE, "\n");
03250 }
03251 
03252 
03253 void mm_log(char *string, long errflg)
03254 {
03255    switch ((short) errflg) {
03256       case NIL:
03257          ast_debug(1, "IMAP Info: %s\n", string);
03258          break;
03259       case PARSE:
03260       case WARN:
03261          ast_log(AST_LOG_WARNING, "IMAP Warning: %s\n", string);
03262          break;
03263       case ERROR:
03264          ast_log(AST_LOG_ERROR, "IMAP Error: %s\n", string);
03265          break;
03266    }
03267 }
03268 
03269 
03270 void mm_dlog(char *string)
03271 {
03272    ast_log(AST_LOG_NOTICE, "%s\n", string);
03273 }
03274 
03275 
03276 void mm_login(NETMBX * mb, char *user, char *pwd, long trial)
03277 {
03278    struct ast_vm_user *vmu;
03279 
03280    ast_debug(4, "Entering callback mm_login\n");
03281 
03282    ast_copy_string(user, mb->user, MAILTMPLEN);
03283 
03284    /* We should only do this when necessary */
03285    if (!ast_strlen_zero(authpassword)) {
03286       ast_copy_string(pwd, authpassword, MAILTMPLEN);
03287    } else {
03288       AST_LIST_TRAVERSE(&users, vmu, list) {
03289          if (!strcasecmp(mb->user, vmu->imapuser)) {
03290             ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
03291             break;
03292          }
03293       }
03294       if (!vmu) {
03295          if ((vmu = find_user_realtime_imapuser(mb->user))) {
03296             ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
03297             free_user(vmu);
03298          }
03299       }
03300    }
03301 }
03302 
03303 
03304 void mm_critical(MAILSTREAM * stream)
03305 {
03306 }
03307 
03308 
03309 void mm_nocritical(MAILSTREAM * stream)
03310 {
03311 }
03312 
03313 
03314 long mm_diskerror(MAILSTREAM * stream, long errcode, long serious)
03315 {
03316    kill (getpid (), SIGSTOP);
03317    return NIL;
03318 }
03319 
03320 
03321 void mm_fatal(char *string)
03322 {
03323    ast_log(AST_LOG_ERROR, "IMAP access FATAL error: %s\n", string);
03324 }
03325 
03326 /* C-client callback to handle quota */
03327 static void mm_parsequota(MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
03328 {
03329    struct vm_state *vms;
03330    char *mailbox = stream->mailbox, *user;
03331    char buf[1024] = "";
03332    unsigned long usage = 0, limit = 0;
03333 
03334    while (pquota) {
03335       usage = pquota->usage;
03336       limit = pquota->limit;
03337       pquota = pquota->next;
03338    }
03339 
03340    if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || (!(vms = get_vm_state_by_imapuser(user, 2)) && !(vms = get_vm_state_by_imapuser(user, 0)))) {
03341       ast_log(AST_LOG_ERROR, "No state found.\n");
03342       return;
03343    }
03344 
03345    ast_debug(3, "User %s usage is %lu, limit is %lu\n", user, usage, limit);
03346 
03347    vms->quota_usage = usage;
03348    vms->quota_limit = limit;
03349 }
03350 
03351 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len)
03352 {
03353    char *start, *eol_pnt;
03354    int taglen;
03355 
03356    if (ast_strlen_zero(header) || ast_strlen_zero(tag))
03357       return NULL;
03358 
03359    taglen = strlen(tag) + 1;
03360    if (taglen < 1)
03361       return NULL;
03362 
03363    if (!(start = strcasestr(header, tag)))
03364       return NULL;
03365 
03366    /* Since we can be called multiple times we should clear our buffer */
03367    memset(buf, 0, len);
03368 
03369    ast_copy_string(buf, start+taglen, len);
03370    if ((eol_pnt = strchr(buf,'\r')) || (eol_pnt = strchr(buf,'\n')))
03371       *eol_pnt = '\0';
03372    return buf;
03373 }
03374 
03375 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len)
03376 {
03377    char *start, *eol_pnt, *quote;
03378 
03379    if (ast_strlen_zero(mailbox))
03380       return NULL;
03381 
03382    if (!(start = strstr(mailbox, "/user=")))
03383       return NULL;
03384 
03385    ast_copy_string(buf, start+6, len);
03386 
03387    if (!(quote = strchr(buf, '"'))) {
03388       if ((eol_pnt = strchr(buf, '/')) || (eol_pnt = strchr(buf, '}'))) {
03389          *eol_pnt = '\0';
03390       }
03391       return buf;
03392    } else {
03393       if ((eol_pnt = strchr(quote + 1, '"'))) {
03394          *eol_pnt = '\0';
03395       }
03396       return quote + 1;
03397    }
03398 }
03399 
03400 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu)
03401 {
03402    struct vm_state *vms_p;
03403 
03404    pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
03405    if ((vms_p = pthread_getspecific(ts_vmstate.key)) && !strcmp(vms_p->imapuser, vmu->imapuser) && !strcmp(vms_p->username, vmu->mailbox)) {
03406       return vms_p;
03407    }
03408    ast_debug(5, "Adding new vmstate for %s\n", vmu->imapuser);
03409    /* XXX: Is this correctly freed always? */
03410    if (!(vms_p = ast_calloc(1, sizeof(*vms_p))))
03411       return NULL;
03412    ast_copy_string(vms_p->imapuser, vmu->imapuser, sizeof(vms_p->imapuser));
03413    ast_copy_string(vms_p->imapfolder, vmu->imapfolder, sizeof(vms_p->imapfolder));
03414    ast_copy_string(vms_p->imapserver, vmu->imapserver, sizeof(vms_p->imapserver));
03415    ast_copy_string(vms_p->imapport, vmu->imapport, sizeof(vms_p->imapport));
03416    ast_copy_string(vms_p->imapflags, vmu->imapflags, sizeof(vms_p->imapflags));
03417    ast_copy_string(vms_p->username, vmu->mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
03418    ast_copy_string(vms_p->context, vmu->context, sizeof(vms_p->context));
03419    vms_p->mailstream = NIL; /* save for access from interactive entry point */
03420    vms_p->imapversion = vmu->imapversion;
03421    ast_debug(5, "Copied %s to %s\n", vmu->imapuser, vms_p->imapuser);
03422    vms_p->updated = 1;
03423    /* set mailbox to INBOX! */
03424    ast_copy_string(vms_p->curbox, mbox(vmu, 0), sizeof(vms_p->curbox));
03425    init_vm_state(vms_p);
03426    vmstate_insert(vms_p);
03427    return vms_p;
03428 }
03429 
03430 static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive)
03431 {
03432    struct vmstate *vlist = NULL;
03433 
03434    if (interactive) {
03435       struct vm_state *vms;
03436       pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
03437       vms = pthread_getspecific(ts_vmstate.key);
03438       return vms;
03439    }
03440 
03441    AST_LIST_LOCK(&vmstates);
03442    AST_LIST_TRAVERSE(&vmstates, vlist, list) {
03443       if (!vlist->vms) {
03444          ast_debug(3, "error: vms is NULL for %s\n", user);
03445          continue;
03446       }
03447       if (vlist->vms->imapversion != imapversion) {
03448          continue;
03449       }
03450       if (!vlist->vms->imapuser) {
03451          ast_debug(3, "error: imapuser is NULL for %s\n", user);
03452          continue;
03453       }
03454 
03455       if (!strcmp(vlist->vms->imapuser, user) && (interactive == 2 || vlist->vms->interactive == interactive)) {
03456          AST_LIST_UNLOCK(&vmstates);
03457          return vlist->vms;
03458       }
03459    }
03460    AST_LIST_UNLOCK(&vmstates);
03461 
03462    ast_debug(3, "%s not found in vmstates\n", user);
03463 
03464    return NULL;
03465 }
03466 
03467 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive)
03468 {
03469 
03470    struct vmstate *vlist = NULL;
03471    const char *local_context = S_OR(context, "default");
03472 
03473    if (interactive) {
03474       struct vm_state *vms;
03475       pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
03476       vms = pthread_getspecific(ts_vmstate.key);
03477       return vms;
03478    }
03479 
03480    AST_LIST_LOCK(&vmstates);
03481    AST_LIST_TRAVERSE(&vmstates, vlist, list) {
03482       if (!vlist->vms) {
03483          ast_debug(3, "error: vms is NULL for %s\n", mailbox);
03484          continue;
03485       }
03486       if (vlist->vms->imapversion != imapversion) {
03487          continue;
03488       }
03489       if (!vlist->vms->username || !vlist->vms->context) {
03490          ast_debug(3, "error: username is NULL for %s\n", mailbox);
03491          continue;
03492       }
03493 
03494       ast_debug(3, "comparing mailbox %s@%s (i=%d) to vmstate mailbox %s@%s (i=%d)\n", mailbox, local_context, interactive, vlist->vms->username, vlist->vms->context, vlist->vms->interactive);
03495 
03496       if (!strcmp(vlist->vms->username, mailbox) && !strcmp(vlist->vms->context, local_context) && vlist->vms->interactive == interactive) {
03497          ast_debug(3, "Found it!\n");
03498          AST_LIST_UNLOCK(&vmstates);
03499          return vlist->vms;
03500       }
03501    }
03502    AST_LIST_UNLOCK(&vmstates);
03503 
03504    ast_debug(3, "%s not found in vmstates\n", mailbox);
03505 
03506    return NULL;
03507 }
03508 
03509 static void vmstate_insert(struct vm_state *vms)
03510 {
03511    struct vmstate *v;
03512    struct vm_state *altvms;
03513 
03514    /* If interactive, it probably already exists, and we should
03515       use the one we already have since it is more up to date.
03516       We can compare the username to find the duplicate */
03517    if (vms->interactive == 1) {
03518       altvms = get_vm_state_by_mailbox(vms->username, vms->context, 0);
03519       if (altvms) {
03520          ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
03521          vms->newmessages = altvms->newmessages;
03522          vms->oldmessages = altvms->oldmessages;
03523          vms->vmArrayIndex = altvms->vmArrayIndex;
03524          /* XXX: no msgArray copying? */
03525          vms->lastmsg = altvms->lastmsg;
03526          vms->curmsg = altvms->curmsg;
03527          /* get a pointer to the persistent store */
03528          vms->persist_vms = altvms;
03529          /* Reuse the mailstream? */
03530 #ifdef REALLY_FAST_EVEN_IF_IT_MEANS_RESOURCE_LEAKS
03531          vms->mailstream = altvms->mailstream;
03532 #else
03533          vms->mailstream = NIL;
03534 #endif
03535       }
03536       return;
03537    }
03538 
03539    if (!(v = ast_calloc(1, sizeof(*v))))
03540       return;
03541 
03542    v->vms = vms;
03543 
03544    ast_debug(3, "Inserting vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
03545 
03546    AST_LIST_LOCK(&vmstates);
03547    AST_LIST_INSERT_TAIL(&vmstates, v, list);
03548    AST_LIST_UNLOCK(&vmstates);
03549 }
03550 
03551 static void vmstate_delete(struct vm_state *vms)
03552 {
03553    struct vmstate *vc = NULL;
03554    struct vm_state *altvms = NULL;
03555 
03556    /* If interactive, we should copy pertinent info
03557       back to the persistent state (to make update immediate) */
03558    if (vms->interactive == 1 && (altvms = vms->persist_vms)) {
03559       ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
03560       altvms->newmessages = vms->newmessages;
03561       altvms->oldmessages = vms->oldmessages;
03562       altvms->updated = 1;
03563       vms->mailstream = mail_close(vms->mailstream);
03564 
03565       /* Interactive states are not stored within the persistent list */
03566       return;
03567    }
03568 
03569    ast_debug(3, "Removing vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
03570 
03571    AST_LIST_LOCK(&vmstates);
03572    AST_LIST_TRAVERSE_SAFE_BEGIN(&vmstates, vc, list) {
03573       if (vc->vms == vms) {
03574          AST_LIST_REMOVE_CURRENT(list);
03575          break;
03576       }
03577    }
03578    AST_LIST_TRAVERSE_SAFE_END
03579    AST_LIST_UNLOCK(&vmstates);
03580    
03581    if (vc) {
03582       ast_mutex_destroy(&vc->vms->lock);
03583       ast_free(vc->vms->msgArray);
03584       vc->vms->msgArray = NULL;
03585       vc->vms->msg_array_max = 0;
03586       /* XXX: is no one supposed to free vms itself? */
03587       ast_free(vc);
03588    } else {
03589       ast_log(AST_LOG_ERROR, "No vmstate found for user:%s, mailbox %s\n", vms->imapuser, vms->username);
03590    }
03591 }
03592 
03593 static void set_update(MAILSTREAM * stream)
03594 {
03595    struct vm_state *vms;
03596    char *mailbox = stream->mailbox, *user;
03597    char buf[1024] = "";
03598 
03599    if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || !(vms = get_vm_state_by_imapuser(user, 0))) {
03600       if (user && option_debug > 2)
03601          ast_log(AST_LOG_WARNING, "User %s mailbox not found for update.\n", user);
03602       return;
03603    }
03604 
03605    ast_debug(3, "User %s mailbox set for update.\n", user);
03606 
03607    vms->updated = 1; /* Set updated flag since mailbox changed */
03608 }
03609 
03610 static void init_vm_state(struct vm_state *vms)
03611 {
03612    vms->msg_array_max = VMSTATE_MAX_MSG_ARRAY;
03613    vms->msgArray = ast_calloc(vms->msg_array_max, sizeof(long));
03614    if (!vms->msgArray) {
03615       /* Out of mem? This can't be good. */
03616       vms->msg_array_max = 0;
03617    }
03618    vms->vmArrayIndex = 0;
03619    ast_mutex_init(&vms->lock);
03620 }
03621 
03622 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro)
03623 {
03624    char *body_content;
03625    char *body_decoded;
03626    char *fn = is_intro ? vms->introfn : vms->fn;
03627    unsigned long len;
03628    unsigned long newlen;
03629    char filename[256];
03630 
03631    if (!body || body == NIL)
03632       return -1;
03633 
03634    ast_mutex_lock(&vms->lock);
03635    body_content = mail_fetchbody(vms->mailstream, vms->msgArray[vms->curmsg], section, &len);
03636    ast_mutex_unlock(&vms->lock);
03637    if (body_content != NIL) {
03638       snprintf(filename, sizeof(filename), "%s.%s", fn, format);
03639       /* ast_debug(1, body_content); */
03640       body_decoded = rfc822_base64((unsigned char *) body_content, len, &newlen);
03641       /* If the body of the file is empty, return an error */
03642       if (!newlen) {
03643          return -1;
03644       }
03645       write_file(filename, (char *) body_decoded, newlen);
03646    } else {
03647       ast_debug(5, "Body of message is NULL.\n");
03648       return -1;
03649    }
03650    return 0;
03651 }
03652 
03653 /*! 
03654  * \brief Get delimiter via mm_list callback 
03655  * \param vms     The voicemail state object
03656  * \param stream
03657  *
03658  * Determines the delimiter character that is used by the underlying IMAP based mail store.
03659  */
03660 /* MUTEX should already be held */
03661 static void get_mailbox_delimiter(struct vm_state *vms, MAILSTREAM *stream) {
03662    char tmp[50];
03663    snprintf(tmp, sizeof(tmp), "{%s}", S_OR(vms->imapserver, imapserver));
03664    mail_list(stream, tmp, "*");
03665 }
03666 
03667 /*! 
03668  * \brief Check Quota for user 
03669  * \param vms a pointer to a vm_state struct, will use the mailstream property of this.
03670  * \param mailbox the mailbox to check the quota for.
03671  *
03672  * Calls imap_getquotaroot, which will populate its results into the vm_state vms input structure.
03673  */
03674 static void check_quota(struct vm_state *vms, char *mailbox) {
03675    ast_mutex_lock(&vms->lock);
03676    mail_parameters(NULL, SET_QUOTA, (void *) mm_parsequota);
03677    ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mailbox);
03678    if (vms && vms->mailstream != NULL) {
03679       imap_getquotaroot(vms->mailstream, mailbox);
03680    } else {
03681       ast_log(AST_LOG_WARNING, "Mailstream not available for mailbox: %s\n", mailbox);
03682    }
03683    ast_mutex_unlock(&vms->lock);
03684 }
03685 
03686 #endif /* IMAP_STORAGE */
03687 
03688 /*! \brief Lock file path
03689  * only return failure if ast_lock_path returns 'timeout',
03690  * not if the path does not exist or any other reason
03691  */
03692 static int vm_lock_path(const char *path)
03693 {
03694    switch (ast_lock_path(path)) {
03695    case AST_LOCK_TIMEOUT:
03696       return -1;
03697    default:
03698       return 0;
03699    }
03700 }
03701 
03702 #define MSG_ID_LEN 256
03703 
03704 /* Used to attach a unique identifier to an msg_id */
03705 static int msg_id_incrementor;
03706 
03707 /*!
03708  * \brief Sets the destination string to a uniquely identifying msg_id string
03709  * \param dst pointer to a character buffer that should contain MSG_ID_LEN characters.
03710  */
03711 static void generate_msg_id(char *dst);
03712 
03713 #ifdef ODBC_STORAGE
03714 struct generic_prepare_struct {
03715    char *sql;
03716    int argc;
03717    char **argv;
03718 };
03719 
03720 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
03721 {
03722    struct generic_prepare_struct *gps = data;
03723    int res, i;
03724    SQLHSTMT stmt;
03725 
03726    res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
03727    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
03728       ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
03729       return NULL;
03730    }
03731    res = SQLPrepare(stmt, (unsigned char *) gps->sql, SQL_NTS);
03732    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
03733       ast_log(AST_LOG_WARNING, "SQL Prepare failed![%s]\n", gps->sql);
03734       SQLFreeHandle(SQL_HANDLE_STMT, stmt);
03735       return NULL;
03736    }
03737    for (i = 0; i < gps->argc; i++)
03738       SQLBindParameter(stmt, i + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->argv[i]), 0, gps->argv[i], 0, NULL);
03739 
03740    return stmt;
03741 }
03742 
03743 static void odbc_update_msg_id(char *dir, int msg_num, char *msg_id)
03744 {
03745    SQLHSTMT stmt;
03746    char sql[PATH_MAX];
03747    struct odbc_obj *obj;
03748    char msg_num_str[20];
03749    char *argv[] = { msg_id, dir, msg_num_str };
03750    struct generic_prepare_struct gps = { .sql = sql, .argc = 3, .argv = argv };
03751 
03752    obj = ast_odbc_request_obj(odbc_database, 0);
03753    if (!obj) {
03754       ast_log(LOG_WARNING, "Unable to update message ID for message %d in %s\n", msg_num, dir);
03755       return;
03756    }
03757 
03758    snprintf(msg_num_str, sizeof(msg_num_str), "%d", msg_num);
03759    snprintf(sql, sizeof(sql), "UPDATE %s SET msg_id=? WHERE dir=? AND msgnum=?", odbc_table);
03760    stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
03761    if (!stmt) {
03762       ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
03763    } else {
03764       SQLFreeHandle(SQL_HANDLE_STMT, stmt);
03765    }
03766    ast_odbc_release_obj(obj);
03767    return;
03768 }
03769 
03770 /*!
03771  * \brief Retrieves a file from an ODBC data store.
03772  * \param dir the path to the file to be retrieved.
03773  * \param msgnum the message number, such as within a mailbox folder.
03774  * 
03775  * This method is used by the RETRIEVE macro when mailboxes are stored in an ODBC back end.
03776  * The purpose is to get the message from the database store to the local file system, so that the message may be played, or the information file may be read.
03777  *
03778  * The file is looked up by invoking a SQL on the odbc_table (default 'voicemessages') using the dir and msgnum input parameters.
03779  * The output is the message information file with the name msgnum and the extension .txt
03780  * and the message file with the extension of its format, in the directory with base file name of the msgnum.
03781  * 
03782  * \return 0 on success, -1 on error.
03783  */
03784 static int retrieve_file(char *dir, int msgnum)
03785 {
03786    int x = 0;
03787    int res;
03788    int fd = -1;
03789    size_t fdlen = 0;
03790    void *fdm = MAP_FAILED;
03791    SQLSMALLINT colcount = 0;
03792    SQLHSTMT stmt;
03793    char sql[PATH_MAX];
03794    char fmt[80]="";
03795    char *c;
03796    char coltitle[256];
03797    SQLSMALLINT collen;
03798    SQLSMALLINT datatype;
03799    SQLSMALLINT decimaldigits;
03800    SQLSMALLINT nullable;
03801    SQLULEN colsize;
03802    SQLLEN colsize2;
03803    FILE *f = NULL;
03804    char rowdata[80];
03805    char fn[PATH_MAX];
03806    char full_fn[PATH_MAX];
03807    char msgnums[80];
03808    char *argv[] = { dir, msgnums };
03809    struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
03810 
03811    struct odbc_obj *obj;
03812    obj = ast_odbc_request_obj(odbc_database, 0);
03813    if (obj) {
03814       ast_copy_string(fmt, vmfmts, sizeof(fmt));
03815       c = strchr(fmt, '|');
03816       if (c)
03817          *c = '\0';
03818       if (!strcasecmp(fmt, "wav49"))
03819          strcpy(fmt, "WAV");
03820       snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
03821       if (msgnum > -1)
03822          make_file(fn, sizeof(fn), dir, msgnum);
03823       else
03824          ast_copy_string(fn, dir, sizeof(fn));
03825 
03826       /* Create the information file */
03827       snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
03828       
03829       if (!(f = fopen(full_fn, "w+"))) {
03830          ast_log(AST_LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
03831          goto yuck;
03832       }
03833       
03834       snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
03835       snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?", odbc_table);
03836       stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
03837       if (!stmt) {
03838          ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
03839          ast_odbc_release_obj(obj);
03840          goto yuck;
03841       }
03842       res = SQLFetch(stmt);
03843       if (res == SQL_NO_DATA) {
03844          SQLFreeHandle (SQL_HANDLE_STMT, stmt);
03845          ast_odbc_release_obj(obj);
03846          goto yuck;
03847       } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
03848          ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
03849          SQLFreeHandle (SQL_HANDLE_STMT, stmt);
03850          ast_odbc_release_obj(obj);
03851          goto yuck;
03852       }
03853       fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
03854       if (fd < 0) {
03855          ast_log(AST_LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
03856          SQLFreeHandle (SQL_HANDLE_STMT, stmt);
03857          ast_odbc_release_obj(obj);
03858          goto yuck;
03859       }
03860       res = SQLNumResultCols(stmt, &colcount);
03861       if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {  
03862          ast_log(AST_LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
03863          SQLFreeHandle (SQL_HANDLE_STMT, stmt);
03864          ast_odbc_release_obj(obj);
03865          goto yuck;
03866       }
03867       if (f) 
03868          fprintf(f, "[message]\n");
03869       for (x = 0; x < colcount; x++) {
03870          rowdata[0] = '\0';
03871          colsize = 0;
03872          collen = sizeof(coltitle);
03873          res = SQLDescribeCol(stmt, x + 1, (unsigned char *) coltitle, sizeof(coltitle), &collen, 
03874                   &datatype, &colsize, &decimaldigits, &nullable);
03875          if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
03876             ast_log(AST_LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
03877             SQLFreeHandle (SQL_HANDLE_STMT, stmt);
03878             ast_odbc_release_obj(obj);
03879             goto yuck;
03880          }
03881          if (!strcasecmp(coltitle, "recording")) {
03882             off_t offset;
03883             res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
03884             fdlen = colsize2;
03885             if (fd > -1) {
03886                char tmp[1]="";
03887                lseek(fd, fdlen - 1, SEEK_SET);
03888                if (write(fd, tmp, 1) != 1) {
03889                   close(fd);
03890                   fd = -1;
03891                   continue;
03892                }
03893                /* Read out in small chunks */
03894                for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
03895                   if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
03896                      ast_log(AST_LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
03897                      SQLFreeHandle(SQL_HANDLE_STMT, stmt);
03898                      ast_odbc_release_obj(obj);
03899                      goto yuck;
03900                   } else {
03901                      res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
03902                      munmap(fdm, CHUNKSIZE);
03903                      if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
03904                         ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
03905                         unlink(full_fn);
03906                         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
03907                         ast_odbc_release_obj(obj);
03908                         goto yuck;
03909                      }
03910                   }
03911                }
03912                if (truncate(full_fn, fdlen) < 0) {
03913                   ast_log(LOG_WARNING, "Unable to truncate '%s': %s\n", full_fn, strerror(errno));
03914                }
03915             }
03916          } else {
03917             res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
03918             if ((res == SQL_NULL_DATA) && (!strcasecmp(coltitle, "msg_id"))) {
03919                char msg_id[MSG_ID_LEN];
03920                generate_msg_id(msg_id);
03921                snprintf(rowdata, sizeof(rowdata), "%s", msg_id);
03922                odbc_update_msg_id(dir, msgnum, msg_id);
03923             } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
03924                ast_log(AST_LOG_WARNING, "SQL Get Data error! coltitle=%s\n[%s]\n\n", coltitle, sql);
03925                SQLFreeHandle (SQL_HANDLE_STMT, stmt);
03926                ast_odbc_release_obj(obj);
03927                goto yuck;
03928             }
03929             if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
03930                fprintf(f, "%s=%s\n", coltitle, rowdata);
03931          }
03932       }
03933       SQLFreeHandle (SQL_HANDLE_STMT, stmt);
03934       ast_odbc_release_obj(obj);
03935    } else
03936       ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
03937 yuck:
03938    if (f)
03939       fclose(f);
03940    if (fd > -1)
03941       close(fd);
03942    return x - 1;
03943 }
03944 
03945 /*!
03946  * \brief Determines the highest message number in use for a given user and mailbox folder.
03947  * \param vmu 
03948  * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
03949  *
03950  * This method is used when mailboxes are stored in an ODBC back end.
03951  * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
03952  *
03953  * \return the value of zero or greater to indicate the last message index in use, -1 to indicate none.
03954 
03955  */
03956 static int last_message_index(struct ast_vm_user *vmu, char *dir)
03957 {
03958    int x = 0;
03959    int res;
03960    SQLHSTMT stmt;
03961    char sql[PATH_MAX];
03962    char rowdata[20];
03963    char *argv[] = { dir };
03964    struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
03965 
03966    struct odbc_obj *obj;
03967    obj = ast_odbc_request_obj(odbc_database, 0);
03968    if (obj) {
03969       snprintf(sql, sizeof(sql), "SELECT msgnum FROM %s WHERE dir=? order by msgnum desc", odbc_table);
03970 
03971       stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
03972       if (!stmt) {
03973          ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
03974          ast_odbc_release_obj(obj);
03975          goto yuck;
03976       }
03977       res = SQLFetch(stmt);
03978       if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
03979          if (res == SQL_NO_DATA) {
03980             ast_log(AST_LOG_DEBUG, "Directory '%s' has no messages and therefore no index was retrieved.\n", dir);
03981          } else {
03982             ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
03983          }
03984 
03985          SQLFreeHandle (SQL_HANDLE_STMT, stmt);
03986          ast_odbc_release_obj(obj);
03987          goto yuck;
03988       }
03989       res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
03990       if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
03991          ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
03992          SQLFreeHandle (SQL_HANDLE_STMT, stmt);
03993          ast_odbc_release_obj(obj);
03994          goto yuck;
03995       }
03996       if (sscanf(rowdata, "%30d", &x) != 1)
03997          ast_log(AST_LOG_WARNING, "Failed to read message index!\n");
03998       SQLFreeHandle (SQL_HANDLE_STMT, stmt);
03999       ast_odbc_release_obj(obj);
04000       return x;
04001    } else
04002       ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
04003 yuck:
04004    return x - 1;
04005 }
04006 
04007 /*!
04008  * \brief Determines if the specified message exists.
04009  * \param dir the folder the mailbox folder to look for messages. 
04010  * \param msgnum the message index to query for.
04011  *
04012  * This method is used when mailboxes are stored in an ODBC back end.
04013  *
04014  * \return greater than zero if the message exists, zero when the message does not exist or on error.
04015  */
04016 static int message_exists(char *dir, int msgnum)
04017 {
04018    int x = 0;
04019    int res;
04020    SQLHSTMT stmt;
04021    char sql[PATH_MAX];
04022    char rowdata[20];
04023    char msgnums[20];
04024    char *argv[] = { dir, msgnums };
04025    struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
04026 
04027    struct odbc_obj *obj;
04028    obj = ast_odbc_request_obj(odbc_database, 0);
04029    if (obj) {
04030       snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
04031       snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?", odbc_table);
04032       stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
04033       if (!stmt) {
04034          ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
04035          ast_odbc_release_obj(obj);
04036          goto yuck;
04037       }
04038       res = SQLFetch(stmt);
04039       if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
04040          ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
04041          SQLFreeHandle (SQL_HANDLE_STMT, stmt);
04042          ast_odbc_release_obj(obj);
04043          goto yuck;
04044       }
04045       res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
04046       if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
04047          ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
04048          SQLFreeHandle (SQL_HANDLE_STMT, stmt);
04049          ast_odbc_release_obj(obj);
04050          goto yuck;
04051       }
04052       if (sscanf(rowdata, "%30d", &x) != 1)
04053          ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
04054       SQLFreeHandle (SQL_HANDLE_STMT, stmt);
04055       ast_odbc_release_obj(obj);
04056    } else
04057       ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
04058 yuck:
04059    return x;
04060 }
04061 
04062 /*!
04063  * \brief returns the number of messages found.
04064  * \param vmu
04065  * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
04066  *
04067  * This method is used when mailboxes are stored in an ODBC back end.
04068  *
04069  * \return The count of messages being zero or more, less than zero on error.
04070  */
04071 static int count_messages(struct ast_vm_user *vmu, char *dir)
04072 {
04073    int x = 0;
04074    int res;
04075    SQLHSTMT stmt;
04076    char sql[PATH_MAX];
04077    char rowdata[20];
04078    char *argv[] = { dir };
04079    struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
04080 
04081    struct odbc_obj *obj;
04082    obj = ast_odbc_request_obj(odbc_database, 0);
04083    if (obj) {
04084       snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?", odbc_table);
04085       stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
04086       if (!stmt) {
04087          ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
04088          ast_odbc_release_obj(obj);
04089          goto yuck;
04090       }
04091       res = SQLFetch(stmt);
04092       if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
04093          ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
04094          SQLFreeHandle (SQL_HANDLE_STMT, stmt);
04095          ast_odbc_release_obj(obj);
04096          goto yuck;
04097       }
04098       res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
04099       if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
04100          ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
04101          SQLFreeHandle (SQL_HANDLE_STMT, stmt);
04102          ast_odbc_release_obj(obj);
04103          goto yuck;
04104       }
04105       if (sscanf(rowdata, "%30d", &x) != 1)
04106          ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
04107       SQLFreeHandle (SQL_HANDLE_STMT, stmt);
04108       ast_odbc_release_obj(obj);
04109       return x;
04110    } else
04111       ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
04112 yuck:
04113    return x - 1;
04114 
04115 }
04116 
04117 /*!
04118  * \brief Deletes a message from the mailbox folder.
04119  * \param sdir The mailbox folder to work in.
04120  * \param smsg The message index to be deleted.
04121  *
04122  * This method is used when mailboxes are stored in an ODBC back end.
04123  * The specified message is directly deleted from the database 'voicemessages' table.
04124  * 
04125  * \return the value greater than zero on success to indicate the number of messages, less than zero on error.
04126  */
04127 static void delete_file(const char *sdir, int smsg)
04128 {
04129    SQLHSTMT stmt;
04130    char sql[PATH_MAX];
04131    char msgnums[20];
04132    char *argv[] = { NULL, msgnums };
04133    struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
04134    struct odbc_obj *obj;
04135 
04136    argv[0] = ast_strdupa(sdir);
04137 
04138    obj = ast_odbc_request_obj(odbc_database, 0);
04139    if (obj) {
04140       snprintf(msgnums, sizeof(msgnums), "%d", smsg);
04141       snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?", odbc_table);
04142       stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
04143       if (!stmt)
04144          ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
04145       else
04146          SQLFreeHandle (SQL_HANDLE_STMT, stmt);
04147       ast_odbc_release_obj(obj);
04148    } else
04149       ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
04150    return;  
04151 }
04152 
04153 /*!
04154  * \brief Copies a voicemail from one mailbox to another.
04155  * \param sdir the folder for which to look for the message to be copied.
04156  * \param smsg the index of the message to be copied.
04157  * \param ddir the destination folder to copy the message into.
04158  * \param dmsg the index to be used for the copied message.
04159  * \param dmailboxuser The user who owns the mailbox tha contains the destination folder.
04160  * \param dmailboxcontext The context for the destination user.
04161  *
04162  * This method is used for the COPY macro when mailboxes are stored in an ODBC back end.
04163  */
04164 static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailboxuser, char *dmailboxcontext)
04165 {
04166    SQLHSTMT stmt;
04167    char sql[512];
04168    char msgnums[20];
04169    char msgnumd[20];
04170    char msg_id[MSG_ID_LEN];
04171    struct odbc_obj *obj;
04172    char *argv[] = { ddir, msgnumd, msg_id, dmailboxuser, dmailboxcontext, sdir, msgnums };
04173    struct generic_prepare_struct gps = { .sql = sql, .argc = 7, .argv = argv };
04174 
04175    generate_msg_id(msg_id);
04176    delete_file(ddir, dmsg);
04177    obj = ast_odbc_request_obj(odbc_database, 0);
04178    if (obj) {
04179       snprintf(msgnums, sizeof(msgnums), "%d", smsg);
04180       snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
04181       snprintf(sql, sizeof(sql), "INSERT INTO %s (dir, msgnum, msg_id, context, macrocontext, callerid, origtime, duration, recording, flag, mailboxuser, mailboxcontext) SELECT ?,?,?,context,macrocontext,callerid,origtime,duration,recording,flag,?,? FROM %s WHERE dir=? AND msgnum=?", odbc_table, odbc_table);
04182       stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
04183       if (!stmt)
04184          ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
04185       else
04186          SQLFreeHandle(SQL_HANDLE_STMT, stmt);
04187       ast_odbc_release_obj(obj);
04188    } else
04189       ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
04190    return;  
04191 }
04192 
04193 struct insert_data {
04194    char *sql;
04195    const char *dir;
04196    const char *msgnums;
04197    void *data;
04198    SQLLEN datalen;
04199    SQLLEN indlen;
04200    const char *context;
04201    const char *macrocontext;
04202    const char *callerid;
04203    const char *origtime;
04204    const char *duration;
04205    const char *mailboxuser;
04206    const char *mailboxcontext;
04207    const char *category;
04208    const char *flag;
04209    const char *msg_id;
04210 };
04211 
04212 static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
04213 {
04214    struct insert_data *data = vdata;
04215    int res;
04216    SQLHSTMT stmt;
04217 
04218    res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
04219    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
04220       ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
04221       SQLFreeHandle(SQL_HANDLE_STMT, stmt);
04222       return NULL;
04223    }
04224 
04225    SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->dir), 0, (void *) data->dir, 0, NULL);
04226    SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->msgnums), 0, (void *) data->msgnums, 0, NULL);
04227    SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, data->datalen, 0, (void *) data->data, data->datalen, &data->indlen);
04228    SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->context), 0, (void *) data->context, 0, NULL);
04229    SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->macrocontext), 0, (void *) data->macrocontext, 0, NULL);
04230    SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->callerid), 0, (void *) data->callerid, 0, NULL);
04231    SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->origtime), 0, (void *) data->origtime, 0, NULL);
04232    SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->duration), 0, (void *) data->duration, 0, NULL);
04233    SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxuser), 0, (void *) data->mailboxuser, 0, NULL);
04234    SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxcontext), 0, (void *) data->mailboxcontext, 0, NULL);
04235    SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->flag), 0, (void *) data->flag, 0, NULL);
04236    SQLBindParameter(stmt, 12, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->msg_id), 0, (void *) data->msg_id, 0, NULL);
04237    if (!ast_strlen_zero(data->category)) {
04238       SQLBindParameter(stmt, 13, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->category), 0, (void *) data->category, 0, NULL);
04239    }
04240    res = SQLExecDirect(stmt, (unsigned char *) data->sql, SQL_NTS);
04241    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
04242       ast_log(AST_LOG_WARNING, "SQL Direct Execute failed!\n");
04243       SQLFreeHandle(SQL_HANDLE_STMT, stmt);
04244       return NULL;
04245    }
04246 
04247    return stmt;
04248 }
04249 
04250 /*!
04251  * \brief Stores a voicemail into the database.
04252  * \param dir the folder the mailbox folder to store the message.
04253  * \param mailboxuser the user owning the mailbox folder.
04254  * \param mailboxcontext
04255  * \param msgnum the message index for the message to be stored.
04256  *
04257  * This method is used when mailboxes are stored in an ODBC back end.
04258  * The message sound file and information file is looked up on the file system. 
04259  * A SQL query is invoked to store the message into the (MySQL) database.
04260  *
04261  * \return the zero on success -1 on error.
04262  */
04263 static int store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum)
04264 {
04265    int res = 0;
04266    int fd = -1;
04267    void *fdm = MAP_FAILED;
04268    off_t fdlen = -1;
04269    SQLHSTMT stmt;
04270    char sql[PATH_MAX];
04271    char msgnums[20];
04272    char fn[PATH_MAX];
04273    char full_fn[PATH_MAX];
04274    char fmt[80]="";
04275    char *c;
04276    struct ast_config *cfg = NULL;
04277    struct odbc_obj *obj;
04278    struct insert_data idata = { .sql = sql, .msgnums = msgnums, .dir = dir, .mailboxuser = mailboxuser, .mailboxcontext = mailboxcontext,
04279       .context = "", .macrocontext = "", .callerid = "", .origtime = "", .duration = "", .category = "", .flag = "", .msg_id = "" };
04280    struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
04281 
04282    delete_file(dir, msgnum);
04283    if (!(obj = ast_odbc_request_obj(odbc_database, 0))) {
04284       ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
04285       return -1;
04286    }
04287 
04288    do {
04289       ast_copy_string(fmt, vmfmts, sizeof(fmt));
04290       c = strchr(fmt, '|');
04291       if (c)
04292          *c = '\0';
04293       if (!strcasecmp(fmt, "wav49"))
04294          strcpy(fmt, "WAV");
04295       snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
04296       if (msgnum > -1)
04297          make_file(fn, sizeof(fn), dir, msgnum);
04298       else
04299          ast_copy_string(fn, dir, sizeof(fn));
04300       snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
04301       cfg = ast_config_load(full_fn, config_flags);
04302       snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
04303       fd = open(full_fn, O_RDWR);
04304       if (fd < 0) {
04305          ast_log(AST_LOG_WARNING, "Open of sound file '%s' failed: %s\n", full_fn, strerror(errno));
04306          res = -1;
04307          break;
04308       }
04309       if (valid_config(cfg)) {
04310          if (!(idata.context = ast_variable_retrieve(cfg, "message", "context"))) {
04311             idata.context = "";
04312          }
04313          if (!(idata.macrocontext = ast_variable_retrieve(cfg, "message", "macrocontext"))) {
04314             idata.macrocontext = "";
04315          }
04316          if (!(idata.callerid = ast_variable_retrieve(cfg, "message", "callerid"))) {
04317             idata.callerid = "";
04318          }
04319          if (!(idata.origtime = ast_variable_retrieve(cfg, "message", "origtime"))) {
04320             idata.origtime = "";
04321          }
04322          if (!(idata.duration = ast_variable_retrieve(cfg, "message", "duration"))) {
04323             idata.duration = "";
04324          }
04325          if (!(idata.category = ast_variable_retrieve(cfg, "message", "category"))) {
04326             idata.category = "";
04327          }
04328          if (!(idata.flag = ast_variable_retrieve(cfg, "message", "flag"))) {
04329             idata.flag = "";
04330          }
04331          if (!(idata.msg_id = ast_variable_retrieve(cfg, "message", "msg_id"))) {
04332             idata.msg_id = "";
04333          }
04334       }
04335       fdlen = lseek(fd, 0, SEEK_END);
04336       if (fdlen < 0 || lseek(fd, 0, SEEK_SET) < 0) {
04337          ast_log(AST_LOG_WARNING, "Failed to process sound file '%s': %s\n", full_fn, strerror(errno));
04338          res = -1;
04339          break;
04340       }
04341       fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
04342       if (fdm == MAP_FAILED) {
04343          ast_log(AST_LOG_WARNING, "Memory map failed for sound file '%s'!\n", full_fn);
04344          res = -1;
04345          break;
04346       } 
04347       idata.data = fdm;
04348       idata.datalen = idata.indlen = fdlen;
04349 
04350       if (!ast_strlen_zero(idata.category)) 
04351          snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,msg_id,category) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", odbc_table); 
04352       else
04353          snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,msg_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", odbc_table);
04354 
04355       if ((stmt = ast_odbc_direct_execute(obj, insert_data_cb, &idata))) {
04356          SQLFreeHandle (SQL_HANDLE_STMT, stmt);
04357       } else {
04358          ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
04359          res = -1;
04360       }
04361    } while (0);
04362    if (obj) {
04363       ast_odbc_release_obj(obj);
04364    }
04365    if (valid_config(cfg))
04366       ast_config_destroy(cfg);
04367    if (fdm != MAP_FAILED)
04368       munmap(fdm, fdlen);
04369    if (fd > -1)
04370       close(fd);
04371    return res;
04372 }
04373 
04374 /*!
04375  * \brief Renames a message in a mailbox folder.
04376  * \param sdir The folder of the message to be renamed.
04377  * \param smsg The index of the message to be renamed.
04378  * \param mailboxuser The user to become the owner of the message after it is renamed. Usually this will be the same as the original owner.
04379  * \param mailboxcontext The context to be set for the message. Usually this will be the same as the original context.
04380  * \param ddir The destination folder for the message to be renamed into
04381  * \param dmsg The destination message for the message to be renamed.
04382  *
04383  * This method is used by the RENAME macro when mailboxes are stored in an ODBC back end.
04384  * The is usually used to resequence the messages in the mailbox, such as to delete messag index 0, it would be called successively to slide all the other messages down one index.
04385  * But in theory, because the SQL query performs an update on (dir, msgnum, mailboxuser, mailboxcontext) in the database, it should be possible to have the message relocated to another mailbox or context as well.
04386  */
04387 static void rename_file(char *sdir, int smsg, char *mailboxuser, char *mailboxcontext, char *ddir, int dmsg)
04388 {
04389    SQLHSTMT stmt;
04390    char sql[PATH_MAX];
04391    char msgnums[20];
04392    char msgnumd[20];
04393    struct odbc_obj *obj;
04394    char *argv[] = { ddir, msgnumd, mailboxuser, mailboxcontext, sdir, msgnums };
04395    struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
04396 
04397    delete_file(ddir, dmsg);
04398    obj = ast_odbc_request_obj(odbc_database, 0);
04399    if (obj) {
04400       snprintf(msgnums, sizeof(msgnums), "%d", smsg);
04401       snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
04402       snprintf(sql, sizeof(sql), "UPDATE %s SET dir=?, msgnum=?, mailboxuser=?, mailboxcontext=? WHERE dir=? AND msgnum=?", odbc_table);
04403       stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
04404       if (!stmt)
04405          ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
04406       else
04407          SQLFreeHandle(SQL_HANDLE_STMT, stmt);
04408       ast_odbc_release_obj(obj);
04409    } else
04410       ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
04411    return;  
04412 }
04413 
04414 /*!
04415  * \brief Removes a voicemail message file.
04416  * \param dir the path to the message file.
04417  * \param msgnum the unique number for the message within the mailbox.
04418  *
04419  * Removes the message content file and the information file.
04420  * This method is used by the DISPOSE macro when mailboxes are stored in an ODBC back end.
04421  * Typical use is to clean up after a RETRIEVE operation. 
04422  * Note that this does not remove the message from the mailbox folders, to do that we would use delete_file().
04423  * \return zero on success, -1 on error.
04424  */
04425 static int remove_file(char *dir, int msgnum)
04426 {
04427    char fn[PATH_MAX];
04428    char full_fn[PATH_MAX];
04429    char msgnums[80];
04430    
04431    if (msgnum > -1) {
04432       snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
04433       make_file(fn, sizeof(fn), dir, msgnum);
04434    } else
04435       ast_copy_string(fn, dir, sizeof(fn));
04436    ast_filedelete(fn, NULL);  
04437    snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
04438    unlink(full_fn);
04439    return 0;
04440 }
04441 #else
04442 #ifndef IMAP_STORAGE
04443 /*!
04444  * \brief Find all .txt files - even if they are not in sequence from 0000.
04445  * \param vmu
04446  * \param dir
04447  *
04448  * This method is used when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
04449  *
04450  * \return the count of messages, zero or more.
04451  */
04452 static int count_messages(struct ast_vm_user *vmu, char *dir)
04453 {
04454 
04455    int vmcount = 0;
04456    DIR *vmdir = NULL;
04457    struct dirent *vment = NULL;
04458 
04459    if (vm_lock_path(dir))
04460       return ERROR_LOCK_PATH;
04461 
04462    if ((vmdir = opendir(dir))) {
04463       while ((vment = readdir(vmdir))) {
04464          if (strlen(vment->d_name) > 7 && !strncmp(vment->d_name + 7, ".txt", 4)) {
04465             vmcount++;
04466          }
04467       }
04468       closedir(vmdir);
04469    }
04470    ast_unlock_path(dir);
04471    
04472    return vmcount;
04473 }
04474 
04475 /*!
04476  * \brief Renames a message in a mailbox folder.
04477  * \param sfn The path to the mailbox information and data file to be renamed.
04478  * \param dfn The path for where the message data and information files will be renamed to.
04479  *
04480  * This method is used by the RENAME macro when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
04481  */
04482 static void rename_file(char *sfn, char *dfn)
04483 {
04484    char stxt[PATH_MAX];
04485    char dtxt[PATH_MAX];
04486    ast_filerename(sfn, dfn, NULL);
04487    snprintf(stxt, sizeof(stxt), "%s.txt", sfn);
04488    snprintf(dtxt, sizeof(dtxt), "%s.txt", dfn);
04489    if (ast_check_realtime("voicemail_data")) {
04490       ast_update_realtime("voicemail_data", "filename", sfn, "filename", dfn, SENTINEL);
04491    }
04492    rename(stxt, dtxt);
04493 }
04494 
04495 /*! 
04496  * \brief Determines the highest message number in use for a given user and mailbox folder.
04497  * \param vmu 
04498  * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
04499  *
04500  * This method is used when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
04501  * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
04502  *
04503  * \note Should always be called with a lock already set on dir.
04504  * \return the value of zero or greaterto indicate the last message index in use, -1 to indicate none.
04505  */
04506 static int last_message_index(struct ast_vm_user *vmu, char *dir)
04507 {
04508    int x;
04509    unsigned char map[MAXMSGLIMIT] = "";
04510    DIR *msgdir;
04511    struct dirent *msgdirent;
04512    int msgdirint;
04513    char extension[4];
04514    int stopcount = 0;
04515 
04516    /* Reading the entire directory into a file map scales better than
04517     * doing a stat repeatedly on a predicted sequence.  I suspect this
04518     * is partially due to stat(2) internally doing a readdir(2) itself to
04519     * find each file. */
04520    if (!(msgdir = opendir(dir))) {
04521       return -1;
04522    }
04523 
04524    while ((msgdirent = readdir(msgdir))) {
04525       if (sscanf(msgdirent->d_name, "msg%30d.%3s", &msgdirint, extension) == 2 && !strcmp(extension, "txt") && msgdirint < MAXMSGLIMIT) {
04526          map[msgdirint] = 1;
04527          stopcount++;
04528          ast_debug(4, "%s map[%d] = %d, count = %d\n", dir, msgdirint, map[msgdirint], stopcount);
04529       }
04530    }
04531    closedir(msgdir);
04532 
04533    for (x = 0; x < vmu->maxmsg; x++) {
04534       if (map[x] == 1) {
04535          stopcount--;
04536       } else if (map[x] == 0 && !stopcount) {
04537          break;
04538       }
04539    }
04540 
04541    return x - 1;
04542 }
04543 
04544 #endif /* #ifndef IMAP_STORAGE */
04545 #endif /* #else of #ifdef ODBC_STORAGE */
04546 #ifndef IMAP_STORAGE
04547 /*!
04548  * \brief Utility function to copy a file.
04549  * \param infile The path to the file to be copied. The file must be readable, it is opened in read only mode.
04550  * \param outfile The path for which to copy the file to. The directory permissions must allow the creation (or truncation) of the file, and allow for opening the file in write only mode.
04551  *
04552  * When the compiler option HARDLINK_WHEN_POSSIBLE is set, the copy operation will attempt to use the hard link facility instead of copy the file (to save disk space). If the link operation fails, it falls back to the copy operation.
04553  * The copy operation copies up to 4096 bytes at once.
04554  *
04555  * \return zero on success, -1 on error.
04556  */
04557 static int copy(char *infile, char *outfile)
04558 {
04559    int ifd;
04560    int ofd;
04561    int res;
04562    int len;
04563    char buf[4096];
04564 
04565 #ifdef HARDLINK_WHEN_POSSIBLE
04566    /* Hard link if possible; saves disk space & is faster */
04567    if (link(infile, outfile)) {
04568 #endif
04569       if ((ifd = open(infile, O_RDONLY)) < 0) {
04570          ast_log(AST_LOG_WARNING, "Unable to open %s in read-only mode: %s\n", infile, strerror(errno));
04571          return -1;
04572       }
04573       if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, VOICEMAIL_FILE_MODE)) < 0) {
04574          ast_log(AST_LOG_WARNING, "Unable to open %s in write-only mode: %s\n", outfile, strerror(errno));
04575          close(ifd);
04576          return -1;
04577       }
04578       do {
04579          len = read(ifd, buf, sizeof(buf));
04580          if (len < 0) {
04581             ast_log(AST_LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
04582             close(ifd);
04583             close(ofd);
04584             unlink(outfile);
04585          } else if (len) {
04586             res = write(ofd, buf, len);
04587             if (errno == ENOMEM || errno == ENOSPC || res != len) {
04588                ast_log(AST_LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno));
04589                close(ifd);
04590                close(ofd);
04591                unlink(outfile);
04592             }
04593          }
04594       } while (len);
04595       close(ifd);
04596       close(ofd);
04597       return 0;
04598 #ifdef HARDLINK_WHEN_POSSIBLE
04599    } else {
04600       /* Hard link succeeded */
04601       return 0;
04602    }
04603 #endif
04604 }
04605 
04606 /*!
04607  * \brief Copies a voicemail information (envelope) file.
04608  * \param frompath
04609  * \param topath 
04610  *
04611  * Every voicemail has the data (.wav) file, and the information file.
04612  * This function performs the file system copying of the information file for a voicemail, handling the internal fields and their values.
04613  * This is used by the COPY macro when not using IMAP storage.
04614  */
04615 static void copy_plain_file(char *frompath, char *topath)
04616 {
04617    char frompath2[PATH_MAX], topath2[PATH_MAX];
04618    struct ast_variable *tmp,*var = NULL;
04619    const char *origmailbox = NULL, *context = NULL, *macrocontext = NULL, *exten = NULL, *priority = NULL, *callerchan = NULL, *callerid = NULL, *origdate = NULL, *origtime = NULL, *category = NULL, *duration = NULL;
04620    ast_filecopy(frompath, topath, NULL);
04621    snprintf(frompath2, sizeof(frompath2), "%s.txt", frompath);
04622    snprintf(topath2, sizeof(topath2), "%s.txt", topath);
04623    if (ast_check_realtime("voicemail_data")) {
04624       var = ast_load_realtime("voicemail_data", "filename", frompath, SENTINEL);
04625       /* This cycle converts ast_variable linked list, to va_list list of arguments, may be there is a better way to do it? */
04626       for (tmp = var; tmp; tmp = tmp->next) {
04627          if (!strcasecmp(tmp->name, "origmailbox")) {
04628             origmailbox = tmp->value;
04629          } else if (!strcasecmp(tmp->name, "context")) {
04630             context = tmp->value;
04631          } else if (!strcasecmp(tmp->name, "macrocontext")) {
04632             macrocontext = tmp->value;
04633          } else if (!strcasecmp(tmp->name, "exten")) {
04634             exten = tmp->value;
04635          } else if (!strcasecmp(tmp->name, "priority")) {
04636             priority = tmp->value;
04637          } else if (!strcasecmp(tmp->name, "callerchan")) {
04638             callerchan = tmp->value;
04639          } else if (!strcasecmp(tmp->name, "callerid")) {
04640             callerid = tmp->value;
04641          } else if (!strcasecmp(tmp->name, "origdate")) {
04642             origdate = tmp->value;
04643          } else if (!strcasecmp(tmp->name, "origtime")) {
04644             origtime = tmp->value;
04645          } else if (!strcasecmp(tmp->name, "category")) {
04646             category = tmp->value;
04647          } else if (!strcasecmp(tmp->name, "duration")) {
04648             duration = tmp->value;
04649          }
04650       }
04651       ast_store_realtime("voicemail_data", "filename", topath, "origmailbox", origmailbox, "context", context, "macrocontext", macrocontext, "exten", exten, "priority", priority, "callerchan", callerchan, "callerid", callerid, "origdate", origdate, "origtime", origtime, "category", category, "duration", duration, SENTINEL);
04652    }
04653    copy(frompath2, topath2);
04654    ast_variables_destroy(var);
04655 }
04656 #endif
04657 
04658 /*! 
04659  * \brief Removes the voicemail sound and information file.
04660  * \param file The path to the sound file. This will be the the folder and message index, without the extension.
04661  *
04662  * This is used by the DELETE macro when voicemails are stored on the file system.
04663  *
04664  * \return zero on success, -1 on error.
04665  */
04666 static int vm_delete(char *file)
04667 {
04668    char *txt;
04669    int txtsize = 0;
04670 
04671    txtsize = (strlen(file) + 5)*sizeof(char);
04672    txt = ast_alloca(txtsize);
04673    /* Sprintf here would safe because we alloca'd exactly the right length,
04674     * but trying to eliminate all sprintf's anyhow
04675     */
04676    if (ast_check_realtime("voicemail_data")) {
04677       ast_destroy_realtime("voicemail_data", "filename", file, SENTINEL);
04678    }
04679    snprintf(txt, txtsize, "%s.txt", file);
04680    unlink(txt);
04681    return ast_filedelete(file, NULL);
04682 }
04683 
04684 /*!
04685  * \brief utility used by inchar(), for base_encode()
04686  */
04687 static int inbuf(struct baseio *bio, FILE *fi)
04688 {
04689    int l;
04690 
04691    if (bio->ateof)
04692       return 0;
04693 
04694    if ((l = fread(bio->iobuf, 1, BASEMAXINLINE, fi)) <= 0) {
04695       if (ferror(fi))
04696          return -1;
04697 
04698       bio->ateof = 1;
04699       return 0;
04700    }
04701 
04702    bio->iolen = l;
04703    bio->iocp = 0;
04704 
04705    return 1;
04706 }
04707 
04708 /*!
04709  * \brief utility used by base_encode()
04710  */
04711 static int inchar(struct baseio *bio, FILE *fi)
04712 {
04713    if (bio->iocp>=bio->iolen) {
04714       if (!inbuf(bio, fi))
04715          return EOF;
04716    }
04717 
04718    return bio->iobuf[bio->iocp++];
04719 }
04720 
04721 /*!
04722  * \brief utility used by base_encode()
04723  */
04724 static int ochar(struct baseio *bio, int c, FILE *so)
04725 {
04726    if (bio->linelength >= BASELINELEN) {
04727       if (fputs(ENDL, so) == EOF) {
04728          return -1;
04729       }
04730 
04731       bio->linelength = 0;
04732    }
04733 
04734    if (putc(((unsigned char) c), so) == EOF) {
04735       return -1;
04736    }
04737 
04738    bio->linelength++;
04739 
04740    return 1;
04741 }
04742 
04743 /*!
04744  * \brief Performs a base 64 encode algorithm on the contents of a File
04745  * \param filename The path to the file to be encoded. Must be readable, file is opened in read mode.
04746  * \param so A FILE handle to the output file to receive the base 64 encoded contents of the input file, identified by filename.
04747  *
04748  * TODO: shouldn't this (and the above 3 support functions) be put into some kind of external utility location, such as funcs/func_base64.c ?
04749  *
04750  * \return zero on success, -1 on error.
04751  */
04752 static int base_encode(char *filename, FILE *so)
04753 {
04754    static const unsigned char dtable[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
04755       'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
04756       'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0',
04757       '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
04758    int i, hiteof = 0;
04759    FILE *fi;
04760    struct baseio bio;
04761 
04762    memset(&bio, 0, sizeof(bio));
04763    bio.iocp = BASEMAXINLINE;
04764 
04765    if (!(fi = fopen(filename, "rb"))) {
04766       ast_log(AST_LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno));
04767       return -1;
04768    }
04769 
04770    while (!hiteof){
04771       unsigned char igroup[3], ogroup[4];
04772       int c, n;
04773 
04774       memset(igroup, 0, sizeof(igroup));
04775 
04776       for (n = 0; n < 3; n++) {
04777          if ((c = inchar(&bio, fi)) == EOF) {
04778             hiteof = 1;
04779             break;
04780          }
04781 
04782          igroup[n] = (unsigned char) c;
04783       }
04784 
04785       if (n > 0) {
04786          ogroup[0]= dtable[igroup[0] >> 2];
04787          ogroup[1]= dtable[((igroup[0] & 3) << 4) | (igroup[1] >> 4)];
04788          ogroup[2]= dtable[((igroup[1] & 0xF) << 2) | (igroup[2] >> 6)];
04789          ogroup[3]= dtable[igroup[2] & 0x3F];
04790 
04791          if (n < 3) {
04792             ogroup[3] = '=';
04793 
04794             if (n < 2)
04795                ogroup[2] = '=';
04796          }
04797 
04798          for (i = 0; i < 4; i++)
04799             ochar(&bio, ogroup[i], so);
04800       }
04801    }
04802 
04803    fclose(fi);
04804    
04805    if (fputs(ENDL, so) == EOF) {
04806       return 0;
04807    }
04808 
04809    return 1;
04810 }
04811 
04812 static void prep_email_sub_vars(struct ast_channel *ast, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, const char *fromfolder, char *cidnum, char *cidname, char *dur, char *date, const char *category, const char *flag)
04813 {
04814    char callerid[256];
04815    char num[12];
04816    char fromdir[256], fromfile[256];
04817    struct ast_config *msg_cfg;
04818    const char *origcallerid, *origtime;
04819    char origcidname[80], origcidnum[80], origdate[80];
04820    int inttime;
04821    struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
04822 
04823    /* Prepare variables for substitution in email body and subject */
04824    pbx_builtin_setvar_helper(ast, "VM_NAME", vmu->fullname);
04825    pbx_builtin_setvar_helper(ast, "VM_DUR", dur);
04826    snprintf(num, sizeof(num), "%d", msgnum);
04827    pbx_builtin_setvar_helper(ast, "VM_MSGNUM", num);
04828    pbx_builtin_setvar_helper(ast, "VM_CONTEXT", context);
04829    pbx_builtin_setvar_helper(ast, "VM_MAILBOX", mailbox);
04830    pbx_builtin_setvar_helper(ast, "VM_CALLERID", (!ast_strlen_zero(cidname) || !ast_strlen_zero(cidnum)) ?
04831       ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, NULL) : "an unknown caller");
04832    pbx_builtin_setvar_helper(ast, "VM_CIDNAME", (!ast_strlen_zero(cidname) ? cidname : "an unknown caller"));
04833    pbx_builtin_setvar_helper(ast, "VM_CIDNUM", (!ast_strlen_zero(cidnum) ? cidnum : "an unknown caller"));
04834    pbx_builtin_setvar_helper(ast, "VM_DATE", date);
04835    pbx_builtin_setvar_helper(ast, "VM_CATEGORY", category ? ast_strdupa(category) : "no category");
04836    pbx_builtin_setvar_helper(ast, "VM_FLAG", flag);
04837 
04838    /* Retrieve info from VM attribute file */
04839    make_dir(fromdir, sizeof(fromdir), vmu->context, vmu->mailbox, fromfolder);
04840    make_file(fromfile, sizeof(fromfile), fromdir, msgnum - 1);
04841    if (strlen(fromfile) < sizeof(fromfile) - 5) {
04842       strcat(fromfile, ".txt");
04843    }
04844    if (!(msg_cfg = ast_config_load(fromfile, config_flags)) || !(valid_config(msg_cfg))) {
04845       ast_debug(1, "Config load for message text file '%s' failed\n", fromfile);
04846       return;
04847    }
04848 
04849    if ((origcallerid = ast_variable_retrieve(msg_cfg, "message", "callerid"))) {
04850       pbx_builtin_setvar_helper(ast, "ORIG_VM_CALLERID", origcallerid);
04851       ast_callerid_split(origcallerid, origcidname, sizeof(origcidname), origcidnum, sizeof(origcidnum));
04852       pbx_builtin_setvar_helper(ast, "ORIG_VM_CIDNAME", origcidname);
04853       pbx_builtin_setvar_helper(ast, "ORIG_VM_CIDNUM", origcidnum);
04854    }
04855 
04856    if ((origtime = ast_variable_retrieve(msg_cfg, "message", "origtime")) && sscanf(origtime, "%30d", &inttime) == 1) {
04857       struct timeval tv = { inttime, };
04858       struct ast_tm tm;
04859       ast_localtime(&tv, &tm, NULL);
04860       ast_strftime_locale(origdate, sizeof(origdate), emaildateformat, &tm, S_OR(vmu->locale, NULL));
04861       pbx_builtin_setvar_helper(ast, "ORIG_VM_DATE", origdate);
04862    }
04863    ast_config_destroy(msg_cfg);
04864 }
04865 
04866 /*!
04867  * \brief Wraps a character sequence in double quotes, escaping occurences of quotes within the string.
04868  * \param from The string to work with.
04869  * \param buf The buffer into which to write the modified quoted string.
04870  * \param maxlen Always zero, but see \see ast_str
04871  * 
04872  * \return The destination string with quotes wrapped on it (the to field).
04873  */
04874 static const char *ast_str_quote(struct ast_str **buf, ssize_t maxlen, const char *from)
04875 {
04876    const char *ptr;
04877 
04878    /* We're only ever passing 0 to maxlen, so short output isn't possible */
04879    ast_str_set(buf, maxlen, "\"");
04880    for (ptr = from; *ptr; ptr++) {
04881       if (*ptr == '"' || *ptr == '\\') {
04882          ast_str_append(buf, maxlen, "\\%c", *ptr);
04883       } else {
04884          ast_str_append(buf, maxlen, "%c", *ptr);
04885       }
04886    }
04887    ast_str_append(buf, maxlen, "\"");
04888 
04889    return ast_str_buffer(*buf);
04890 }
04891 
04892 /*! \brief
04893  * fill in *tm for current time according to the proper timezone, if any.
04894  * \return tm so it can be used as a function argument.
04895  */
04896 static const struct ast_tm *vmu_tm(const struct ast_vm_user *vmu, struct ast_tm *tm)
04897 {
04898    const struct vm_zone *z = NULL;
04899    struct timeval t = ast_tvnow();
04900 
04901    /* Does this user have a timezone specified? */
04902    if (!ast_strlen_zero(vmu->zonetag)) {
04903       /* Find the zone in the list */
04904       AST_LIST_LOCK(&zones);
04905       AST_LIST_TRAVERSE(&zones, z, list) {
04906          if (!strcmp(z->name, vmu->zonetag))
04907             break;
04908       }
04909       AST_LIST_UNLOCK(&zones);
04910    }
04911    ast_localtime(&t, tm, z ? z->timezone : NULL);
04912    return tm;
04913 }
04914 
04915 /*!\brief Check if the string would need encoding within the MIME standard, to
04916  * avoid confusing certain mail software that expects messages to be 7-bit
04917  * clean.
04918  */
04919 static int check_mime(const char *str)
04920 {
04921    for (; *str; str++) {
04922       if (*str > 126 || *str < 32 || strchr("()<>@,:;/\"[]?.=", *str)) {
04923          return 1;
04924       }
04925    }
04926    return 0;
04927 }
04928 
04929 /*!\brief Encode a string according to the MIME rules for encoding strings
04930  * that are not 7-bit clean or contain control characters.
04931  *
04932  * Additionally, if the encoded string would exceed the MIME limit of 76
04933  * characters per line, then the encoding will be broken up into multiple
04934  * sections, separated by a space character, in order to facilitate
04935  * breaking up the associated header across multiple lines.
04936  *
04937  * \param end An expandable buffer for holding the result
04938  * \param maxlen Always zero, but see \see ast_str
04939  * \param start A string to be encoded
04940  * \param preamble The length of the first line already used for this string,
04941  * to ensure that each line maintains a maximum length of 76 chars.
04942  * \param postamble the length of any additional characters appended to the
04943  * line, used to ensure proper field wrapping.
04944  * \retval The encoded string.
04945  */
04946 static const char *ast_str_encode_mime(struct ast_str **end, ssize_t maxlen, const char *start, size_t preamble, size_t postamble)
04947 {
04948    struct ast_str *tmp = ast_str_alloca(80);
04949    int first_section = 1;
04950 
04951    ast_str_reset(*end);
04952    ast_str_set(&tmp, -1, "=?%s?Q?", charset);
04953    for (; *start; start++) {
04954       int need_encoding = 0;
04955       if (*start < 33 || *start > 126 || strchr("()<>@,:;/\"[]?.=_", *start)) {
04956          need_encoding = 1;
04957       }
04958       if ((first_section && need_encoding && preamble + ast_str_strlen(tmp) > 70) ||
04959          (first_section && !need_encoding && preamble + ast_str_strlen(tmp) > 72) ||
04960          (!first_section && need_encoding && ast_str_strlen(tmp) > 70) ||
04961          (!first_section && !need_encoding && ast_str_strlen(tmp) > 72)) {
04962          /* Start new line */
04963          ast_str_append(end, maxlen, "%s%s?=", first_section ? "" : " ", ast_str_buffer(tmp));
04964          ast_str_set(&tmp, -1, "=?%s?Q?", charset);
04965          first_section = 0;
04966       }
04967       if (need_encoding && *start == ' ') {
04968          ast_str_append(&tmp, -1, "_");
04969       } else if (need_encoding) {
04970          ast_str_append(&tmp, -1, "=%hhX", *start);
04971       } else {
04972          ast_str_append(&tmp, -1, "%c", *start);
04973       }
04974    }
04975    ast_str_append(end, maxlen, "%s%s?=%s", first_section ? "" : " ", ast_str_buffer(tmp), ast_str_strlen(tmp) + postamble > 74 ? " " : "");
04976    return ast_str_buffer(*end);
04977 }
04978 
04979 /*!
04980  * \brief Creates the email file to be sent to indicate a new voicemail exists for a user.
04981  * \param p The output file to generate the email contents into.
04982  * \param srcemail The email address to send the email to, presumably the email address for the owner of the mailbox.
04983  * \param vmu The voicemail user who is sending the voicemail.
04984  * \param msgnum The message index in the mailbox folder.
04985  * \param context 
04986  * \param mailbox The voicemail box to read the voicemail to be notified in this email.
04987  * \param fromfolder
04988  * \param cidnum The caller ID number.
04989  * \param cidname The caller ID name.
04990  * \param attach the name of the sound file to be attached to the email, if attach_user_voicemail == 1.
04991  * \param attach2 
04992  * \param format The message sound file format. i.e. .wav
04993  * \param duration The time of the message content, in seconds.
04994  * \param attach_user_voicemail if 1, the sound file is attached to the email.
04995  * \param chan
04996  * \param category
04997  * \param imap if == 1, indicates the target folder for the email notification to be sent to will be an IMAP mailstore. This causes additional mailbox headers to be set, which would facilitate searching for the email in the destination IMAP folder.
04998  * \param flag, msg_id
04999  *
05000  * The email body, and base 64 encoded attachement (if any) are stored to the file identified by *p. This method does not actually send the email.  That is done by invoking the configure 'mailcmd' and piping this generated file into it, or with the sendemail() function.
05001  */
05002 static void make_email_file(FILE *p,
05003       char *srcemail,
05004       struct ast_vm_user *vmu,
05005       int msgnum,
05006       char *context,
05007       char *mailbox,
05008       const char *fromfolder,
05009       char *cidnum,
05010       char *cidname,
05011       char *attach,
05012       char *attach2,
05013       char *format,
05014       int duration,
05015       int attach_user_voicemail,
05016       struct ast_channel *chan,
05017       const char *category,
05018       int imap,
05019       const char *flag,
05020       const char *msg_id)
05021 {
05022    char date[256];
05023    char host[MAXHOSTNAMELEN] = "";
05024    char who[256];
05025    char bound[256];
05026    char dur[256];
05027    struct ast_tm tm;
05028    char enc_cidnum[256] = "", enc_cidname[256] = "";
05029    struct ast_str *str1 = ast_str_create(16), *str2 = ast_str_create(16);
05030    char *greeting_attachment; 
05031    char filename[256];
05032    int first_line;
05033    char *emailsbuf;
05034    char *email;
05035 
05036    if (!str1 || !str2) {
05037       ast_free(str1);
05038       ast_free(str2);
05039       return;
05040    }
05041 
05042    if (cidnum) {
05043       strip_control_and_high(cidnum, enc_cidnum, sizeof(enc_cidnum));
05044    }
05045    if (cidname) {
05046       strip_control_and_high(cidname, enc_cidname, sizeof(enc_cidname));
05047    }
05048    gethostname(host, sizeof(host) - 1);
05049 
05050    if (strchr(srcemail, '@')) {
05051       ast_copy_string(who, srcemail, sizeof(who));
05052    } else {
05053       snprintf(who, sizeof(who), "%s@%s", srcemail, host);
05054    }
05055 
05056    greeting_attachment = strrchr(ast_strdupa(attach), '/');
05057    if (greeting_attachment) {
05058       *greeting_attachment++ = '\0';
05059    }
05060 
05061    snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
05062    ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
05063    fprintf(p, "Date: %s" ENDL, date);
05064 
05065    /* Set date format for voicemail mail */
05066    ast_strftime_locale(date, sizeof(date), emaildateformat, &tm, S_OR(vmu->locale, NULL));
05067 
05068    if (!ast_strlen_zero(fromstring)) {
05069       struct ast_channel *ast;
05070       if ((ast = ast_dummy_channel_alloc())) {
05071          char *ptr;
05072          prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, fromfolder, enc_cidnum, enc_cidname, dur, date, category, flag);
05073          ast_str_substitute_variables(&str1, 0, ast, fromstring);
05074 
05075          if (check_mime(ast_str_buffer(str1))) {
05076             first_line = 1;
05077             ast_str_encode_mime(&str2, 0, ast_str_buffer(str1), strlen("From: "), strlen(who) + 3);
05078             while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
05079                *ptr = '\0';
05080                fprintf(p, "%s %s" ENDL, first_line ? "From:" : "", ast_str_buffer(str2));
05081                first_line = 0;
05082                /* Substring is smaller, so this will never grow */
05083                ast_str_set(&str2, 0, "%s", ptr + 1);
05084             }
05085             fprintf(p, "%s %s <%s>" ENDL, first_line ? "From:" : "", ast_str_buffer(str2), who);
05086          } else {
05087             fprintf(p, "From: %s <%s>" ENDL, ast_str_quote(&str2, 0, ast_str_buffer(str1)), who);
05088          }
05089          ast = ast_channel_unref(ast);
05090       } else {
05091          ast_log(AST_LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
05092       }
05093    } else {
05094       fprintf(p, "From: Asterisk PBX <%s>" ENDL, who);
05095    }
05096 
05097    emailsbuf = ast_strdupa(vmu->email);
05098    fprintf(p, "To:");
05099    first_line = 1;
05100    while ((email = strsep(&emailsbuf, "|"))) {
05101       char *next = emailsbuf;
05102       if (check_mime(vmu->fullname)) {
05103          char *ptr;
05104          ast_str_encode_mime(&str2, 0, vmu->fullname, first_line ? strlen("To: ") : 0, strlen(email) + 3 + (next ? strlen(",") : 0));
05105          while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
05106             *ptr = '\0';
05107             fprintf(p, " %s" ENDL, ast_str_buffer(str2));
05108             /* Substring is smaller, so this will never grow */
05109             ast_str_set(&str2, 0, "%s", ptr + 1);
05110          }
05111          fprintf(p, " %s <%s>%s" ENDL, ast_str_buffer(str2), email, next ? "," : "");
05112       } else {
05113          fprintf(p, " %s <%s>%s" ENDL, ast_str_quote(&str2, 0, vmu->fullname), email, next ? "," : "");
05114       }
05115       first_line = 0;
05116    }
05117 
05118    if (!ast_strlen_zero(emailsubject) || !ast_strlen_zero(vmu->emailsubject)) {
05119       char *e_subj = !ast_strlen_zero(vmu->emailsubject) ? vmu->emailsubject : emailsubject;
05120       struct ast_channel *ast;
05121       if ((ast = ast_dummy_channel_alloc())) {
05122          prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, fromfolder, cidnum, cidname, dur, date, category, flag);
05123          ast_str_substitute_variables(&str1, 0, ast, e_subj);
05124          if (check_mime(ast_str_buffer(str1))) {
05125             char *ptr;
05126             first_line = 1;
05127             ast_str_encode_mime(&str2, 0, ast_str_buffer(str1), strlen("Subject: "), 0);
05128             while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
05129                *ptr = '\0';
05130                fprintf(p, "%s %s" ENDL, first_line ? "Subject:" : "", ast_str_buffer(str2));
05131                first_line = 0;
05132                /* Substring is smaller, so this will never grow */
05133                ast_str_set(&str2, 0, "%s", ptr + 1);
05134             }
05135             fprintf(p, "%s %s" ENDL, first_line ? "Subject:" : "", ast_str_buffer(str2));
05136          } else {
05137             fprintf(p, "Subject: %s" ENDL, ast_str_buffer(str1));
05138          }
05139          ast = ast_channel_unref(ast);
05140       } else {
05141          ast_log(AST_LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
05142       }
05143    } else if (ast_test_flag((&globalflags), VM_PBXSKIP)) {
05144       if (ast_strlen_zero(flag)) {
05145          fprintf(p, "Subject: New message %d in mailbox %s" ENDL, msgnum + 1, mailbox);
05146       } else {
05147          fprintf(p, "Subject: New %s message %d in mailbox %s" ENDL, flag, msgnum + 1, mailbox);
05148       }
05149    } else {
05150       if (ast_strlen_zero(flag)) {
05151          fprintf(p, "Subject: [PBX]: New message %d in mailbox %s" ENDL, msgnum + 1, mailbox);
05152       } else {
05153          fprintf(p, "Subject: [PBX]: New %s message %d in mailbox %s" ENDL, flag, msgnum + 1, mailbox);
05154       }
05155    }
05156 
05157    fprintf(p, "Message-ID: <Asterisk-%d-%u-%s-%d@%s>" ENDL, msgnum + 1,
05158       (unsigned int) ast_random(), mailbox, (int) getpid(), host);
05159    if (imap) {
05160       /* additional information needed for IMAP searching */
05161       fprintf(p, "X-Asterisk-VM-Message-Num: %d" ENDL, msgnum + 1);
05162       /* fprintf(p, "X-Asterisk-VM-Orig-Mailbox: %s" ENDL, ext); */
05163       fprintf(p, "X-Asterisk-VM-Server-Name: %s" ENDL, fromstring);
05164       fprintf(p, "X-Asterisk-VM-Context: %s" ENDL, context);
05165 #ifdef IMAP_STORAGE
05166       fprintf(p, "X-Asterisk-VM-Extension: %s" ENDL, (!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : mailbox));
05167 #else
05168       fprintf(p, "X-Asterisk-VM-Extension: %s" ENDL, mailbox);
05169 #endif
05170       /* flag added for Urgent */
05171       fprintf(p, "X-Asterisk-VM-Flag: %s" ENDL, flag);
05172       fprintf(p, "X-Asterisk-VM-Priority: %d" ENDL, chan ? ast_channel_priority(chan) : 0);
05173       fprintf(p, "X-Asterisk-VM-Caller-channel: %s" ENDL, chan ? ast_channel_name(chan) : "");
05174       fprintf(p, "X-Asterisk-VM-Caller-ID-Num: %s" ENDL, enc_cidnum);
05175       fprintf(p, "X-Asterisk-VM-Caller-ID-Name: %s" ENDL, enc_cidname);
05176       fprintf(p, "X-Asterisk-VM-Duration: %d" ENDL, duration);
05177       if (!ast_strlen_zero(category)) {
05178          fprintf(p, "X-Asterisk-VM-Category: %s" ENDL, category);
05179       } else {
05180          fprintf(p, "X-Asterisk-VM-Category: " ENDL);
05181       }
05182       fprintf(p, "X-Asterisk-VM-Message-Type: %s" ENDL, msgnum > -1 ? "Message" : greeting_attachment);
05183       fprintf(p, "X-Asterisk-VM-Orig-date: %s" ENDL, date);
05184       fprintf(p, "X-Asterisk-VM-Orig-time: %ld" ENDL, (long) time(NULL));
05185       fprintf(p, "X-Asterisk-VM-Message-ID: %s" ENDL, msg_id);
05186    }
05187    if (!ast_strlen_zero(cidnum)) {
05188       fprintf(p, "X-Asterisk-CallerID: %s" ENDL, enc_cidnum);
05189    }
05190    if (!ast_strlen_zero(cidname)) {
05191       fprintf(p, "X-Asterisk-CallerIDName: %s" ENDL, enc_cidname);
05192    }
05193    fprintf(p, "MIME-Version: 1.0" ENDL);
05194    if (attach_user_voicemail) {
05195       /* Something unique. */
05196       snprintf(bound, sizeof(bound), "----voicemail_%d%s%d%u", msgnum + 1, mailbox,
05197          (int) getpid(), (unsigned int) ast_random());
05198 
05199       fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"" ENDL, bound);
05200       fprintf(p, ENDL ENDL "This is a multi-part message in MIME format." ENDL ENDL);
05201       fprintf(p, "--%s" ENDL, bound);
05202    }
05203    fprintf(p, "Content-Type: text/plain; charset=%s" ENDL "Content-Transfer-Encoding: 8bit" ENDL ENDL, charset);
05204    if (emailbody || vmu->emailbody) {
05205       char* e_body = vmu->emailbody ? vmu->emailbody : emailbody;
05206       struct ast_channel *ast;
05207       if ((ast = ast_dummy_channel_alloc())) {
05208          prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, fromfolder, cidnum, cidname, dur, date, category, flag);
05209          ast_str_substitute_variables(&str1, 0, ast, e_body);
05210 #ifdef IMAP_STORAGE
05211             {
05212                /* Convert body to native line terminators for IMAP backend */
05213                char *line = ast_str_buffer(str1), *next;
05214                do {
05215                   /* Terminate line before outputting it to the file */
05216                   if ((next = strchr(line, '\n'))) {
05217                      *next++ = '\0';
05218                   }
05219                   fprintf(p, "%s" ENDL, line);
05220                   line = next;
05221                } while (!ast_strlen_zero(line));
05222             }
05223 #else
05224          fprintf(p, "%s" ENDL, ast_str_buffer(str1));
05225 #endif
05226          ast = ast_channel_unref(ast);
05227       } else {
05228          ast_log(AST_LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
05229       }
05230    } else if (msgnum > -1) {
05231       if (strcmp(vmu->mailbox, mailbox)) {
05232          /* Forwarded type */
05233          struct ast_config *msg_cfg;
05234          const char *v;
05235          int inttime;
05236          char fromdir[256], fromfile[256], origdate[80] = "", origcallerid[80] = "";
05237          struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
05238          /* Retrieve info from VM attribute file */
05239          make_dir(fromdir, sizeof(fromdir), vmu->context, vmu->mailbox, fromfolder);
05240          make_file(fromfile, sizeof(fromfile), fromdir, msgnum);
05241          if (strlen(fromfile) < sizeof(fromfile) - 5) {
05242             strcat(fromfile, ".txt");
05243          }
05244          if ((msg_cfg = ast_config_load(fromfile, config_flags)) && valid_config(msg_cfg)) {
05245             if ((v = ast_variable_retrieve(msg_cfg, "message", "callerid"))) {
05246                ast_copy_string(origcallerid, v, sizeof(origcallerid));
05247             }
05248 
05249             /* You might be tempted to do origdate, except that a) it's in the wrong
05250              * format, and b) it's missing for IMAP recordings. */
05251             if ((v = ast_variable_retrieve(msg_cfg, "message", "origtime")) && sscanf(v, "%30d", &inttime) == 1) {
05252                struct timeval tv = { inttime, };
05253                struct ast_tm tm;
05254                ast_localtime(&tv, &tm, NULL);
05255                ast_strftime_locale(origdate, sizeof(origdate), emaildateformat, &tm, S_OR(vmu->locale, NULL));
05256             }
05257             fprintf(p, "Dear %s:" ENDL ENDL "\tJust wanted to let you know you were just forwarded"
05258                " a %s long message (number %d)" ENDL "in mailbox %s from %s, on %s" ENDL
05259                "(originally sent by %s on %s)" ENDL "so you might want to check it when you get a"
05260                " chance.  Thanks!" ENDL ENDL "\t\t\t\t--Asterisk" ENDL ENDL, vmu->fullname, dur,
05261                msgnum + 1, mailbox, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")),
05262                date, origcallerid, origdate);
05263             ast_config_destroy(msg_cfg);
05264          } else {
05265             goto plain_message;
05266          }
05267       } else {
05268 plain_message:
05269          fprintf(p, "Dear %s:" ENDL ENDL "\tJust wanted to let you know you were just left a "
05270             "%s long message (number %d)" ENDL "in mailbox %s from %s, on %s so you might" ENDL
05271             "want to check it when you get a chance.  Thanks!" ENDL ENDL "\t\t\t\t--Asterisk"
05272             ENDL ENDL, vmu->fullname, dur, msgnum + 1, mailbox,
05273             (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
05274       }
05275    } else {
05276       fprintf(p, "This message is to let you know that your greeting was changed on %s." ENDL
05277             "Please do not delete this message, lest your greeting vanish with it." ENDL ENDL, date);
05278    }
05279 
05280    if (imap || attach_user_voicemail) {
05281       if (!ast_strlen_zero(attach2)) {
05282          snprintf(filename, sizeof(filename), "msg%04d.%s", msgnum, format);
05283          ast_debug(5, "creating second attachment filename %s\n", filename);
05284          add_email_attachment(p, vmu, format, attach, greeting_attachment, mailbox, bound, filename, 0, msgnum);
05285          snprintf(filename, sizeof(filename), "msgintro%04d.%s", msgnum, format);
05286          ast_debug(5, "creating attachment filename %s\n", filename);
05287          add_email_attachment(p, vmu, format, attach2, greeting_attachment, mailbox, bound, filename, 1, msgnum);
05288       } else {
05289          snprintf(filename, sizeof(filename), "msg%04d.%s", msgnum, format);
05290          ast_debug(5, "creating attachment filename %s, no second attachment.\n", filename);
05291          add_email_attachment(p, vmu, format, attach, greeting_attachment, mailbox, bound, filename, 1, msgnum);
05292       }
05293    }
05294    ast_free(str1);
05295    ast_free(str2);
05296 }
05297 
05298 static int add_email_attachment(FILE *p, struct ast_vm_user *vmu, char *format, char *attach, char *greeting_attachment, char *mailbox, char *bound, char *filename, int last, int msgnum)
05299 {
05300    char tmpdir[256], newtmp[256];
05301    char fname[256];
05302    char tmpcmd[256];
05303    int tmpfd = -1;
05304    int soxstatus = 0;
05305 
05306    /* Eww. We want formats to tell us their own MIME type */
05307    char *ctype = (!strcasecmp(format, "ogg")) ? "application/" : "audio/x-";
05308 
05309    if (vmu->volgain < -.001 || vmu->volgain > .001) {
05310       create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, vmu->mailbox, "tmp");
05311       snprintf(newtmp, sizeof(newtmp), "%s/XXXXXX", tmpdir);
05312       tmpfd = mkstemp(newtmp);
05313       chmod(newtmp, VOICEMAIL_FILE_MODE & ~my_umask);
05314       ast_debug(3, "newtmp: %s\n", newtmp);
05315       if (tmpfd > -1) {
05316          snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, attach, format, newtmp, format);
05317          if ((soxstatus = ast_safe_system(tmpcmd)) == 0) {
05318             attach = newtmp;
05319             ast_debug(3, "VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", attach, format, vmu->volgain, mailbox);
05320          } else {
05321             ast_log(LOG_WARNING, "Sox failed to re-encode %s.%s: %s (have you installed support for all sox file formats?)\n", attach, format,
05322                soxstatus == 1 ? "Problem with command line options" : "An error occurred during file processing");
05323             ast_log(LOG_WARNING, "Voicemail attachment will have no volume gain.\n");
05324          }
05325       }
05326    }
05327    fprintf(p, "--%s" ENDL, bound);
05328    if (msgnum > -1)
05329       fprintf(p, "Content-Type: %s%s; name=\"%s\"" ENDL, ctype, format, filename);
05330    else
05331       fprintf(p, "Content-Type: %s%s; name=\"%s.%s\"" ENDL, ctype, format, greeting_attachment, format);
05332    fprintf(p, "Content-Transfer-Encoding: base64" ENDL);
05333    fprintf(p, "Content-Description: Voicemail sound attachment." ENDL);
05334    if (msgnum > -1)
05335       fprintf(p, "Content-Disposition: attachment; filename=\"%s\"" ENDL ENDL, filename);
05336    else
05337       fprintf(p, "Content-Disposition: attachment; filename=\"%s.%s\"" ENDL ENDL, greeting_attachment, format);
05338    snprintf(fname, sizeof(fname), "%s.%s", attach, format);
05339    base_encode(fname, p);
05340    if (last)
05341       fprintf(p, ENDL ENDL "--%s--" ENDL "." ENDL, bound);
05342    if (tmpfd > -1) {
05343       if (soxstatus == 0) {
05344          unlink(fname);
05345       }
05346       close(tmpfd);
05347       unlink(newtmp);
05348    }
05349    return 0;
05350 }
05351 
05352 static int sendmail(char *srcemail,
05353       struct ast_vm_user *vmu,
05354       int msgnum,
05355       char *context,
05356       char *mailbox,
05357       const char *fromfolder,
05358       char *cidnum,
05359       char *cidname,
05360       char *attach,
05361       char *attach2,
05362       char *format,
05363       int duration,
05364       int attach_user_voicemail,
05365       struct ast_channel *chan,
05366       const char *category,
05367       const char *flag,
05368       const char *msg_id)
05369 {
05370    FILE *p = NULL;
05371    char tmp[80] = "/tmp/astmail-XXXXXX";
05372    char tmp2[256];
05373    char *stringp;
05374 
05375    if (vmu && ast_strlen_zero(vmu->email)) {
05376       ast_log(AST_LOG_WARNING, "E-mail address missing for mailbox [%s].  E-mail will not be sent.\n", vmu->mailbox);
05377       return(0);
05378    }
05379 
05380    /* Mail only the first format */
05381    format = ast_strdupa(format);
05382    stringp = format;
05383    strsep(&stringp, "|");
05384 
05385    if (!strcmp(format, "wav49"))
05386       format = "WAV";
05387    ast_debug(3, "Attaching file '%s', format '%s', uservm is '%d', global is %u\n", attach, format, attach_user_voicemail, ast_test_flag((&globalflags), VM_ATTACH));
05388    /* Make a temporary file instead of piping directly to sendmail, in case the mail
05389       command hangs */
05390    if ((p = vm_mkftemp(tmp)) == NULL) {
05391       ast_log(AST_LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
05392       return -1;
05393    } else {
05394       make_email_file(p, srcemail, vmu, msgnum, context, mailbox, fromfolder, cidnum, cidname, attach, attach2, format, duration, attach_user_voicemail, chan, category, 0, flag, msg_id);
05395       fclose(p);
05396       snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
05397       ast_safe_system(tmp2);
05398       ast_debug(1, "Sent mail to %s with command '%s'\n", vmu->email, mailcmd);
05399    }
05400    return 0;
05401 }
05402 
05403 static int sendpage(char *srcemail, char *pager, int msgnum, char *context, char *mailbox, const char *fromfolder, char *cidnum, char *cidname, int duration, struct ast_vm_user *vmu, const char *category, const char *flag)
05404 {
05405    char enc_cidnum[256], enc_cidname[256];
05406    char date[256];
05407    char host[MAXHOSTNAMELEN] = "";
05408    char who[256];
05409    char dur[PATH_MAX];
05410    char tmp[80] = "/tmp/astmail-XXXXXX";
05411    char tmp2[PATH_MAX];
05412    struct ast_tm tm;
05413    FILE *p;
05414    struct ast_str *str1 = ast_str_create(16), *str2 = ast_str_create(16);
05415 
05416    if (!str1 || !str2) {
05417       ast_free(str1);
05418       ast_free(str2);
05419       return -1;
05420    }
05421 
05422    if (cidnum) {
05423       strip_control_and_high(cidnum, enc_cidnum, sizeof(enc_cidnum));
05424    }
05425    if (cidname) {
05426       strip_control_and_high(cidname, enc_cidname, sizeof(enc_cidname));
05427    }
05428 
05429    if ((p = vm_mkftemp(tmp)) == NULL) {
05430       ast_log(AST_LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
05431       ast_free(str1);
05432       ast_free(str2);
05433       return -1;
05434    }
05435    gethostname(host, sizeof(host)-1);
05436    if (strchr(srcemail, '@')) {
05437       ast_copy_string(who, srcemail, sizeof(who));
05438    } else {
05439       snprintf(who, sizeof(who), "%s@%s", srcemail, host);
05440    }
05441    snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
05442    ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
05443    fprintf(p, "Date: %s\n", date);
05444 
05445    /* Reformat for custom pager format */
05446    ast_strftime_locale(date, sizeof(date), pagerdateformat, vmu_tm(vmu, &tm), S_OR(vmu->locale, NULL));
05447 
05448    if (!ast_strlen_zero(pagerfromstring)) {
05449       struct ast_channel *ast;
05450       if ((ast = ast_dummy_channel_alloc())) {
05451          char *ptr;
05452          prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, fromfolder, enc_cidnum, enc_cidname, dur, date, category, flag);
05453          ast_str_substitute_variables(&str1, 0, ast, pagerfromstring);
05454 
05455          if (check_mime(ast_str_buffer(str1))) {
05456             int first_line = 1;
05457             ast_str_encode_mime(&str2, 0, ast_str_buffer(str1), strlen("From: "), strlen(who) + 3);
05458             while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
05459                *ptr = '\0';
05460                fprintf(p, "%s %s" ENDL, first_line ? "From:" : "", ast_str_buffer(str2));
05461                first_line = 0;
05462                /* Substring is smaller, so this will never grow */
05463                ast_str_set(&str2, 0, "%s", ptr + 1);
05464             }
05465             fprintf(p, "%s %s <%s>" ENDL, first_line ? "From:" : "", ast_str_buffer(str2), who);
05466          } else {
05467             fprintf(p, "From: %s <%s>" ENDL, ast_str_quote(&str2, 0, ast_str_buffer(str1)), who);
05468          }
05469          ast = ast_channel_unref(ast);
05470       } else {
05471          ast_log(AST_LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
05472       }
05473    } else {
05474       fprintf(p, "From: Asterisk PBX <%s>" ENDL, who);
05475    }
05476 
05477    if (check_mime(vmu->fullname)) {
05478       int first_line = 1;
05479       char *ptr;
05480       ast_str_encode_mime(&str2, 0, vmu->fullname, strlen("To: "), strlen(pager) + 3);
05481       while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
05482          *ptr = '\0';
05483          fprintf(p, "%s %s" ENDL, first_line ? "To:" : "", ast_str_buffer(str2));
05484          first_line = 0;
05485          /* Substring is smaller, so this will never grow */
05486          ast_str_set(&str2, 0, "%s", ptr + 1);
05487       }
05488       fprintf(p, "%s %s <%s>" ENDL, first_line ? "To:" : "", ast_str_buffer(str2), pager);
05489    } else {
05490       fprintf(p, "To: %s <%s>" ENDL, ast_str_quote(&str2, 0, vmu->fullname), pager);
05491    }
05492 
05493    if (!ast_strlen_zero(pagersubject)) {
05494       struct ast_channel *ast;
05495       if ((ast = ast_dummy_channel_alloc())) {
05496          prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, fromfolder, cidnum, cidname, dur, date, category, flag);
05497          ast_str_substitute_variables(&str1, 0, ast, pagersubject);
05498          if (check_mime(ast_str_buffer(str1))) {
05499             int first_line = 1;
05500             char *ptr;
05501             ast_str_encode_mime(&str2, 0, ast_str_buffer(str1), strlen("Subject: "), 0);
05502             while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
05503                *ptr = '\0';
05504                fprintf(p, "%s %s" ENDL, first_line ? "Subject:" : "", ast_str_buffer(str2));
05505                first_line = 0;
05506                /* Substring is smaller, so this will never grow */
05507                ast_str_set(&str2, 0, "%s", ptr + 1);
05508             }
05509             fprintf(p, "%s %s" ENDL, first_line ? "Subject:" : "", ast_str_buffer(str2));
05510          } else {
05511             fprintf(p, "Subject: %s" ENDL, ast_str_buffer(str1));
05512          }
05513          ast = ast_channel_unref(ast);
05514       } else {
05515          ast_log(AST_LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
05516       }
05517    } else {
05518       if (ast_strlen_zero(flag)) {
05519          fprintf(p, "Subject: New VM\n\n");
05520       } else {
05521          fprintf(p, "Subject: New %s VM\n\n", flag);
05522       }
05523    }
05524 
05525    if (pagerbody) {
05526       struct ast_channel *ast;
05527       if ((ast = ast_dummy_channel_alloc())) {
05528          prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, fromfolder, cidnum, cidname, dur, date, category, flag);
05529          ast_str_substitute_variables(&str1, 0, ast, pagerbody);
05530          fprintf(p, "%s" ENDL, ast_str_buffer(str1));
05531          ast = ast_channel_unref(ast);
05532       } else {
05533          ast_log(AST_LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
05534       }
05535    } else {
05536       fprintf(p, "New %s long %s msg in box %s\n"
05537             "from %s, on %s", dur, flag, mailbox, (cidname ? cidname : (cidnum ? cidnum : "unknown")), date);
05538    }
05539 
05540    fclose(p);
05541    snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
05542    ast_safe_system(tmp2);
05543    ast_debug(1, "Sent page to %s with command '%s'\n", pager, mailcmd);
05544    ast_free(str1);
05545    ast_free(str2);
05546    return 0;
05547 }
05548 
05549 /*!
05550  * \brief Gets the current date and time, as formatted string.
05551  * \param s The buffer to hold the output formatted date.
05552  * \param len the length of the buffer. Used to prevent buffer overflow in ast_strftime.
05553  * 
05554  * The date format string used is "%a %b %e %r UTC %Y".
05555  * 
05556  * \return zero on success, -1 on error.
05557  */
05558 static int get_date(char *s, int len)
05559 {
05560    struct ast_tm tm;
05561    struct timeval t = ast_tvnow();
05562 
05563    ast_localtime(&t, &tm, "UTC");
05564 
05565    return ast_strftime(s, len, "%a %b %e %r UTC %Y", &tm);
05566 }
05567 
05568 static int invent_message(struct ast_channel *chan, char *context, char *ext, int busy, char *ecodes)
05569 {
05570    int res;
05571    char fn[PATH_MAX];
05572    char dest[PATH_MAX];
05573 
05574    snprintf(fn, sizeof(fn), "%s%s/%s/greet", VM_SPOOL_DIR, context, ext);
05575 
05576    if ((res = create_dirpath(dest, sizeof(dest), context, ext, ""))) {
05577       ast_log(AST_LOG_WARNING, "Failed to make directory(%s)\n", fn);
05578       return -1;
05579    }
05580 
05581    RETRIEVE(fn, -1, ext, context);
05582    if (ast_fileexists(fn, NULL, NULL) > 0) {
05583       res = ast_stream_and_wait(chan, fn, ecodes);
05584       if (res) {
05585          DISPOSE(fn, -1);
05586          return res;
05587       }
05588    } else {
05589       /* Dispose just in case */
05590       DISPOSE(fn, -1);
05591       res = ast_stream_and_wait(chan, "vm-theperson", ecodes);
05592       if (res)
05593          return res;
05594       res = ast_say_digit_str(chan, ext, ecodes, ast_channel_language(chan));
05595       if (res)
05596          return res;
05597    }
05598    res = ast_stream_and_wait(chan, busy ? "vm-isonphone" : "vm-isunavail", ecodes);
05599    return res;
05600 }
05601 
05602 static void free_zone(struct vm_zone *z)
05603 {
05604    ast_free(z);
05605 }
05606 
05607 #ifdef ODBC_STORAGE
05608 static int inboxcount2(const char *mailbox, int *urgentmsgs, int *newmsgs, int *oldmsgs)
05609 {
05610    int x = -1;
05611    int res;
05612    SQLHSTMT stmt = NULL;
05613    char sql[PATH_MAX];
05614    char rowdata[20];
05615    char tmp[PATH_MAX] = "";
05616    struct odbc_obj *obj = NULL;
05617    char *context;
05618    struct generic_prepare_struct gps = { .sql = sql, .argc = 0 };
05619 
05620    if (newmsgs)
05621       *newmsgs = 0;
05622    if (oldmsgs)
05623       *oldmsgs = 0;
05624    if (urgentmsgs)
05625       *urgentmsgs = 0;
05626 
05627    /* If no mailbox, return immediately */
05628    if (ast_strlen_zero(mailbox))
05629       return 0;
05630 
05631    ast_copy_string(tmp, mailbox, sizeof(tmp));
05632 
05633    if (strchr(mailbox, ' ') || strchr(mailbox, ',')) {
05634       int u, n, o;
05635       char *next, *remaining = tmp;
05636       while ((next = strsep(&remaining, " ,"))) {
05637          if (inboxcount2(next, urgentmsgs ? &u : NULL, &n, &o)) {
05638             return -1;
05639          }
05640          if (urgentmsgs) {
05641             *urgentmsgs += u;
05642          }
05643          if (newmsgs) {
05644             *newmsgs += n;
05645          }
05646          if (oldmsgs) {
05647             *oldmsgs += o;
05648          }
05649       }
05650       return 0;
05651    }
05652 
05653    context = strchr(tmp, '@');
05654    if (context) {
05655       *context = '\0';
05656       context++;
05657    } else
05658       context = "default";
05659 
05660    if ((obj = ast_odbc_request_obj(odbc_database, 0))) {
05661       do {
05662          if (newmsgs) {
05663             snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "INBOX");
05664             if (!(stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps))) {
05665                ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
05666                break;
05667             }
05668             res = SQLFetch(stmt);
05669             if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
05670                ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
05671                break;
05672             }
05673             res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
05674             if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
05675                ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
05676                break;
05677             }
05678             *newmsgs = atoi(rowdata);
05679             SQLFreeHandle (SQL_HANDLE_STMT, stmt);
05680          }
05681 
05682          if (oldmsgs) {
05683             snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "Old");
05684             if (!(stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps))) {
05685                ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
05686                break;
05687             }
05688             res = SQLFetch(stmt);
05689             if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
05690                ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
05691                break;
05692             }
05693             res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
05694             if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
05695                ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
05696                break;
05697             }
05698             SQLFreeHandle(SQL_HANDLE_STMT, stmt);
05699             *oldmsgs = atoi(rowdata);
05700          }
05701 
05702          if (urgentmsgs) {
05703             snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "Urgent");
05704             if (!(stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps))) {
05705                ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
05706                break;
05707             }
05708             res = SQLFetch(stmt);
05709             if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
05710                ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
05711                break;
05712             }
05713             res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
05714             if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
05715                ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
05716                break;
05717             }
05718             *urgentmsgs = atoi(rowdata);
05719          }
05720 
05721          x = 0;
05722       } while (0);
05723    } else {
05724       ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
05725    }
05726 
05727    if (stmt) {
05728       SQLFreeHandle (SQL_HANDLE_STMT, stmt);
05729    }
05730    if (obj) {
05731       ast_odbc_release_obj(obj);
05732    }
05733    return x;
05734 }
05735 
05736 /*!
05737  * \brief Gets the number of messages that exist in a mailbox folder.
05738  * \param mailbox_id
05739  * \param folder
05740  * 
05741  * This method is used when ODBC backend is used.
05742  * \return The number of messages in this mailbox folder (zero or more).
05743  */
05744 static int messagecount(const char *mailbox_id, const char *folder)
05745 {
05746    struct odbc_obj *obj = NULL;
05747    char *context;
05748    char *mailbox;
05749    int nummsgs = 0;
05750    int res;
05751    SQLHSTMT stmt = NULL;
05752    char sql[PATH_MAX];
05753    char rowdata[20];
05754    struct generic_prepare_struct gps = { .sql = sql, .argc = 0 };
05755 
05756    /* If no mailbox, return immediately */
05757    if (ast_strlen_zero(mailbox_id)
05758       || separate_mailbox(ast_strdupa(mailbox_id), &mailbox, &context)) {
05759       return 0;
05760    }
05761 
05762    if (ast_strlen_zero(folder)) {
05763       folder = "INBOX";
05764    }
05765 
05766    obj = ast_odbc_request_obj(odbc_database, 0);
05767    if (obj) {
05768       if (!strcmp(folder, "INBOX")) {
05769          snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/INBOX' OR dir = '%s%s/%s/Urgent'", odbc_table, VM_SPOOL_DIR, context, mailbox, VM_SPOOL_DIR, context, mailbox);
05770       } else {
05771          snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, mailbox, folder);
05772       }
05773       stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
05774       if (!stmt) {
05775          ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
05776          goto yuck;
05777       }
05778       res = SQLFetch(stmt);
05779       if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
05780          ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
05781          SQLFreeHandle (SQL_HANDLE_STMT, stmt);
05782          goto yuck;
05783       }
05784       res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
05785       if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
05786          ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
05787          SQLFreeHandle (SQL_HANDLE_STMT, stmt);
05788          goto yuck;
05789       }
05790       nummsgs = atoi(rowdata);
05791       SQLFreeHandle (SQL_HANDLE_STMT, stmt);
05792    } else
05793       ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
05794 
05795 yuck:
05796    if (obj)
05797       ast_odbc_release_obj(obj);
05798    return nummsgs;
05799 }
05800 
05801 /** 
05802  * \brief Determines if the given folder has messages.
05803  * \param mailbox The @ delimited string for user@context. If no context is found, uses 'default' for the context.
05804  * 
05805  * This function is used when the mailbox is stored in an ODBC back end.
05806  * This invokes the messagecount(). Here we are interested in the presence of messages (> 0) only, not the actual count.
05807  * \return 1 if the folder has one or more messages. zero otherwise.
05808  */
05809 static int has_voicemail(const char *mailboxes, const char *folder)
05810 {
05811    char *parse;
05812    char *mailbox;
05813 
05814    parse = ast_strdupa(mailboxes);
05815    while ((mailbox = strsep(&parse, ",&"))) {
05816       if (messagecount(mailbox, folder)) {
05817          return 1;
05818       }
05819    }
05820    return 0;
05821 }
05822 #endif
05823 #ifndef IMAP_STORAGE
05824 /*! 
05825  * \brief Copies a message from one mailbox to another.
05826  * \param chan
05827  * \param vmu
05828  * \param imbox
05829  * \param msgnum
05830  * \param duration
05831  * \param recip
05832  * \param fmt
05833  * \param dir
05834  * \param flag, dest_folder
05835  *
05836  * This is only used by file storage based mailboxes.
05837  *
05838  * \return zero on success, -1 on error.
05839  */
05840 static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir, const char *flag, const char *dest_folder)
05841 {
05842    char fromdir[PATH_MAX], todir[PATH_MAX], frompath[PATH_MAX], topath[PATH_MAX];
05843    const char *frombox = mbox(vmu, imbox);
05844    const char *userfolder;
05845    int recipmsgnum;
05846    int res = 0;
05847 
05848    ast_log(AST_LOG_NOTICE, "Copying message from %s@%s to %s@%s\n", vmu->mailbox, vmu->context, recip->mailbox, recip->context);
05849 
05850    if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) { /* If urgent, copy to Urgent folder */
05851       userfolder = "Urgent";
05852    } else if (!ast_strlen_zero(dest_folder)) {
05853       userfolder = dest_folder;
05854    } else {
05855       userfolder = "INBOX";
05856    }
05857 
05858    create_dirpath(todir, sizeof(todir), recip->context, recip->mailbox, userfolder);
05859 
05860    if (!dir)
05861       make_dir(fromdir, sizeof(fromdir), vmu->context, vmu->mailbox, frombox);
05862    else
05863       ast_copy_string(fromdir, dir, sizeof(fromdir));
05864 
05865    make_file(frompath, sizeof(frompath), fromdir, msgnum);
05866    make_dir(todir, sizeof(todir), recip->context, recip->mailbox, userfolder);
05867 
05868    if (vm_lock_path(todir))
05869       return ERROR_LOCK_PATH;
05870 
05871    recipmsgnum = last_message_index(recip, todir) + 1;
05872    if (recipmsgnum < recip->maxmsg - (imbox ? 0 : inprocess_count(vmu->mailbox, vmu->context, 0))) {
05873       make_file(topath, sizeof(topath), todir, recipmsgnum);
05874 #ifndef ODBC_STORAGE
05875       if (EXISTS(fromdir, msgnum, frompath, chan ? ast_channel_language(chan) : "")) {
05876          COPY(fromdir, msgnum, todir, recipmsgnum, recip->mailbox, recip->context, frompath, topath);
05877       } else {
05878 #endif
05879          /* If we are prepending a message for ODBC, then the message already
05880           * exists in the database, but we want to force copying from the
05881           * filesystem (since only the FS contains the prepend). */
05882          copy_plain_file(frompath, topath);
05883          STORE(todir, recip->mailbox, recip->context, recipmsgnum, chan, recip, fmt, duration, NULL, NULL, NULL);
05884          vm_delete(topath);
05885 #ifndef ODBC_STORAGE
05886       }
05887 #endif
05888    } else {
05889       ast_log(AST_LOG_ERROR, "Recipient mailbox %s@%s is full\n", recip->mailbox, recip->context);
05890       res = -1;
05891    }
05892    ast_unlock_path(todir);
05893    if (chan) {
05894       struct ast_party_caller *caller = ast_channel_caller(chan);
05895       notify_new_message(chan, recip, NULL, recipmsgnum, duration, fmt,
05896          S_COR(caller->id.number.valid, caller->id.number.str, NULL),
05897          S_COR(caller->id.name.valid, caller->id.name.str, NULL),
05898          flag);
05899    }
05900 
05901    return res;
05902 }
05903 #endif
05904 #if !(defined(IMAP_STORAGE) || defined(ODBC_STORAGE))
05905 
05906 static int messagecount(const char *mailbox_id, const char *folder)
05907 {
05908    char *context;
05909    char *mailbox;
05910 
05911    if (ast_strlen_zero(mailbox_id)
05912       || separate_mailbox(ast_strdupa(mailbox_id), &mailbox, &context)) {
05913       return 0;
05914    }
05915 
05916    return __has_voicemail(context, mailbox, folder, 0) + (folder && strcmp(folder, "INBOX") ? 0 : __has_voicemail(context, mailbox, "Urgent", 0));
05917 }
05918 
05919 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit)
05920 {
05921    DIR *dir;
05922    struct dirent *de;
05923    char fn[256];
05924    int ret = 0;
05925 
05926    /* If no mailbox, return immediately */
05927    if (ast_strlen_zero(mailbox))
05928       return 0;
05929 
05930    if (ast_strlen_zero(folder))
05931       folder = "INBOX";
05932    if (ast_strlen_zero(context))
05933       context = "default";
05934 
05935    snprintf(fn, sizeof(fn), "%s%s/%s/%s", VM_SPOOL_DIR, context, mailbox, folder);
05936 
05937    if (!(dir = opendir(fn)))
05938       return 0;
05939 
05940    while ((de = readdir(dir))) {
05941       if (!strncasecmp(de->d_name, "msg", 3)) {
05942          if (shortcircuit) {
05943             ret = 1;
05944             break;
05945          } else if (!strncasecmp(de->d_name + 8, "txt", 3)) {
05946             ret++;
05947          }
05948       }
05949    }
05950 
05951    closedir(dir);
05952 
05953    return ret;
05954 }
05955 
05956 /** 
05957  * \brief Determines if the given folder has messages.
05958  * \param mailbox The \@ delimited string for user\@context. If no context is found, uses 'default' for the context.
05959  * \param folder the folder to look in
05960  *
05961  * This function is used when the mailbox is stored in a filesystem back end.
05962  * This invokes the __has_voicemail(). Here we are interested in the presence of messages (> 0) only, not the actual count.
05963  * \return 1 if the folder has one or more messages. zero otherwise.
05964  */
05965 static int has_voicemail(const char *mailbox, const char *folder)
05966 {
05967    char tmp[256], *tmp2 = tmp, *box, *context;
05968    ast_copy_string(tmp, mailbox, sizeof(tmp));
05969    if (ast_strlen_zero(folder)) {
05970       folder = "INBOX";
05971    }
05972    while ((box = strsep(&tmp2, ",&"))) {
05973       if ((context = strchr(box, '@')))
05974          *context++ = '\0';
05975       else
05976          context = "default";
05977       if (__has_voicemail(context, box, folder, 1))
05978          return 1;
05979       /* If we are checking INBOX, we should check Urgent as well */
05980       if (!strcmp(folder, "INBOX") && __has_voicemail(context, box, "Urgent", 1)) {
05981          return 1;
05982       }
05983    }
05984    return 0;
05985 }
05986 
05987 
05988 static int inboxcount2(const char *mailbox, int *urgentmsgs, int *newmsgs, int *oldmsgs)
05989 {
05990    char tmp[256];
05991    char *context;
05992 
05993    /* If no mailbox, return immediately */
05994    if (ast_strlen_zero(mailbox))
05995       return 0;
05996 
05997    if (newmsgs)
05998       *newmsgs = 0;
05999    if (oldmsgs)
06000       *oldmsgs = 0;
06001    if (urgentmsgs)
06002       *urgentmsgs = 0;
06003 
06004    if (strchr(mailbox, ',')) {
06005       int tmpnew, tmpold, tmpurgent;
06006       char *mb, *cur;
06007 
06008       ast_copy_string(tmp, mailbox, sizeof(tmp));
06009       mb = tmp;
06010       while ((cur = strsep(&mb, ", "))) {
06011          if (!ast_strlen_zero(cur)) {
06012             if (inboxcount2(cur, urgentmsgs ? &tmpurgent : NULL, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
06013                return -1;
06014             else {
06015                if (newmsgs)
06016                   *newmsgs += tmpnew; 
06017                if (oldmsgs)
06018                   *oldmsgs += tmpold;
06019                if (urgentmsgs)
06020                   *urgentmsgs += tmpurgent;
06021             }
06022          }
06023       }
06024       return 0;
06025    }
06026 
06027    ast_copy_string(tmp, mailbox, sizeof(tmp));
06028    
06029    if ((context = strchr(tmp, '@')))
06030       *context++ = '\0';
06031    else
06032       context = "default";
06033 
06034    if (newmsgs)
06035       *newmsgs = __has_voicemail(context, tmp, "INBOX", 0);
06036    if (oldmsgs)
06037       *oldmsgs = __has_voicemail(context, tmp, "Old", 0);
06038    if (urgentmsgs)
06039       *urgentmsgs = __has_voicemail(context, tmp, "Urgent", 0);
06040 
06041    return 0;
06042 }
06043 
06044 #endif
06045 
06046 /* Exactly the same function for file-based, ODBC-based, and IMAP-based, so why create 3 different copies? */
06047 static int inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
06048 {
06049    int urgentmsgs = 0;
06050    int res = inboxcount2(mailbox, &urgentmsgs, newmsgs, oldmsgs);
06051    if (newmsgs) {
06052       *newmsgs += urgentmsgs;
06053    }
06054    return res;
06055 }
06056 
06057 static void run_externnotify(char *context, char *extension, const char *flag)
06058 {
06059    char arguments[255];
06060    char ext_context[256] = "";
06061    int newvoicemails = 0, oldvoicemails = 0, urgentvoicemails = 0;
06062    struct ast_smdi_mwi_message *mwi_msg;
06063 
06064    if (!ast_strlen_zero(context))
06065       snprintf(ext_context, sizeof(ext_context), "%s@%s", extension, context);
06066    else
06067       ast_copy_string(ext_context, extension, sizeof(ext_context));
06068 
06069    if (smdi_iface) {
06070       if (ast_app_has_voicemail(ext_context, NULL)) 
06071          ast_smdi_mwi_set(smdi_iface, extension);
06072       else
06073          ast_smdi_mwi_unset(smdi_iface, extension);
06074 
06075       if ((mwi_msg = ast_smdi_mwi_message_wait_station(smdi_iface, SMDI_MWI_WAIT_TIMEOUT, extension))) {
06076          ast_log(AST_LOG_ERROR, "Error executing SMDI MWI change for %s\n", extension);
06077          if (!strncmp(mwi_msg->cause, "INV", 3))
06078             ast_log(AST_LOG_ERROR, "Invalid MWI extension: %s\n", mwi_msg->fwd_st);
06079          else if (!strncmp(mwi_msg->cause, "BLK", 3))
06080             ast_log(AST_LOG_WARNING, "MWI light was already on or off for %s\n", mwi_msg->fwd_st);
06081          ast_log(AST_LOG_WARNING, "The switch reported '%s'\n", mwi_msg->cause);
06082          ao2_ref(mwi_msg, -1);
06083       } else {
06084          ast_debug(1, "Successfully executed SMDI MWI change for %s\n", extension);
06085       }
06086    }
06087 
06088    if (!ast_strlen_zero(externnotify)) {
06089       if (inboxcount2(ext_context, &urgentvoicemails, &newvoicemails, &oldvoicemails)) {
06090          ast_log(AST_LOG_ERROR, "Problem in calculating number of voicemail messages available for extension %s\n", extension);
06091       } else {
06092          snprintf(arguments, sizeof(arguments), "%s %s %s %d %d %d &",
06093             externnotify, S_OR(context, "\"\""),
06094             extension, newvoicemails,
06095             oldvoicemails, urgentvoicemails);
06096          ast_debug(1, "Executing %s\n", arguments);
06097          ast_safe_system(arguments);
06098       }
06099    }
06100 }
06101 
06102 /*!
06103  * \brief Variables used for saving a voicemail.
06104  *
06105  * This includes the record gain, mode flags, and the exit context of the chanel that was used for leaving the voicemail.
06106  */
06107 struct leave_vm_options {
06108    unsigned int flags;
06109    signed char record_gain;
06110    char *exitcontext;
06111 };
06112 
06113 static void generate_msg_id(char *dst)
06114 {
06115    /* msg id is time of msg_id generation plus an incrementing value
06116     * called each time a new msg_id is generated. This should achieve uniqueness,
06117     * but only in single system solutions.
06118     */
06119    unsigned int unique_counter = ast_atomic_fetchadd_int(&msg_id_incrementor, +1);
06120    snprintf(dst, MSG_ID_LEN, "%ld-%08x", (long) time(NULL), unique_counter);
06121 }
06122 
06123 /*!
06124  * \internal
06125  * \brief Creates a voicemail based on a specified file to a mailbox.
06126  * \param recdata A vm_recording_data containing filename and voicemail txt info.
06127  * \retval -1 failure
06128  * \retval 0 success
06129  *
06130  * This is installed to the app.h voicemail functions and accommodates all voicemail
06131  * storage methods. It should probably be broken out along with leave_voicemail at
06132  * some point in the future.
06133  *
06134  * This function currently only works for a single recipient and only uses the format
06135  * specified in recording_ext.
06136  */
06137 static int msg_create_from_file(struct ast_vm_recording_data *recdata)
06138 {
06139    /* voicemail recipient structure */
06140    struct ast_vm_user *recipient; /* points to svm once it's been created */
06141    struct ast_vm_user svm; /* struct storing the voicemail recipient */
06142 
06143    /* File paths */
06144    char tmpdir[PATH_MAX]; /* directory temp files are stored in */
06145    char tmptxtfile[PATH_MAX]; /* tmp file for voicemail txt file */
06146    char desttxtfile[PATH_MAX]; /* final destination for txt file */
06147    char tmpaudiofile[PATH_MAX]; /* tmp file where audio is stored */
06148    char dir[PATH_MAX]; /* destination for tmp files on completion */
06149    char destination[PATH_MAX]; /* destination with msgXXXX.  Basically <dir>/msgXXXX */
06150 
06151    /* stuff that only seems to be needed for IMAP */
06152    #ifdef IMAP_STORAGE
06153    struct vm_state *vms = NULL;
06154    char ext_context[256] = "";
06155    char *fmt = ast_strdupa(recdata->recording_ext);
06156    int newmsgs = 0;
06157    int oldmsgs = 0;
06158    #endif
06159 
06160    /* miscellaneous operational variables */
06161    int res = 0; /* Used to store error codes from functions */
06162    int txtdes /* File descriptor for the text file used to write the voicemail info */;
06163    FILE *txt; /* FILE pointer to text file used to write the voicemail info */
06164    char date[256]; /* string used to hold date of the voicemail (only used for ODBC) */
06165    int msgnum; /* the 4 digit number designated to the voicemail */
06166    int duration = 0; /* Length of the audio being recorded in seconds */
06167    struct ast_filestream *recording_fs; /*used to read the recording to get duration data */
06168 
06169    /* We aren't currently doing anything with category, since it comes from a channel variable and
06170     * this function doesn't use channels, but this function could add that as an argument later. */
06171    const char *category = NULL; /* pointless for now */
06172    char msg_id[MSG_ID_LEN];
06173 
06174    /* Start by checking to see if the file actually exists... */
06175    if (!(ast_fileexists(recdata->recording_file, recdata->recording_ext, NULL))) {
06176       ast_log(LOG_ERROR, "File: %s not found.\n", recdata->recording_file);
06177       return -1;
06178    }
06179 
06180    if (!(recipient = find_user(&svm, recdata->context, recdata->mailbox))) {
06181       ast_log(LOG_ERROR, "No entry in voicemail config file for '%s@%s'\n", recdata->mailbox, recdata->context);
06182       return -1;
06183    }
06184 
06185    /* determine duration in seconds */
06186    if ((recording_fs = ast_readfile(recdata->recording_file, recdata->recording_ext, NULL, 0, 0, VOICEMAIL_DIR_MODE))) {
06187       if (!ast_seekstream(recording_fs, 0, SEEK_END)) {
06188          long framelength = ast_tellstream(recording_fs);
06189          int sample_rate = ast_ratestream(recording_fs);
06190          if (sample_rate) {
06191             duration = (int) (framelength / sample_rate);
06192          } else {
06193             ast_log(LOG_ERROR,"Unable to determine sample rate of recording %s\n", recdata->recording_file);
06194          }
06195       }
06196       ast_closestream(recording_fs);
06197    }
06198 
06199    /* If the duration was below the minimum duration for the user, let's just drop the whole thing now */
06200    if (duration < recipient->minsecs) {
06201       ast_log(LOG_NOTICE, "Copying recording to voicemail %s@%s skipped because duration was shorter than "
06202                "minmessage of recipient\n", recdata->mailbox, recdata->context);
06203       return -1;
06204    }
06205 
06206    /* Note that this number must be dropped back to a net sum of zero before returning from this function */
06207 
06208    if ((res = create_dirpath(tmpdir, sizeof(tmpdir), recipient->context, recdata->mailbox, "tmp"))) {
06209       ast_log(LOG_ERROR, "Failed to make directory.\n");
06210    }
06211 
06212    snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir);
06213    txtdes = mkstemp(tmptxtfile);
06214    if (txtdes < 0) {
06215       chmod(tmptxtfile, VOICEMAIL_FILE_MODE & ~my_umask);
06216       /* Something screwed up.  Abort. */
06217       ast_log(AST_LOG_ERROR, "Unable to create message file: %s\n", strerror(errno));
06218       free_user(recipient);
06219       return -1;
06220    }
06221 
06222    /* Store information */
06223    txt = fdopen(txtdes, "w+");
06224    if (txt) {
06225       generate_msg_id(msg_id);
06226       get_date(date, sizeof(date));
06227       fprintf(txt,
06228          ";\n"
06229          "; Message Information file\n"
06230          ";\n"
06231          "[message]\n"
06232          "origmailbox=%s\n"
06233          "context=%s\n"
06234          "macrocontext=%s\n"
06235          "exten=%s\n"
06236          "rdnis=Unknown\n"
06237          "priority=%d\n"
06238          "callerchan=%s\n"
06239          "callerid=%s\n"
06240          "origdate=%s\n"
06241          "origtime=%ld\n"
06242          "category=%s\n"
06243          "msg_id=%s\n"
06244          "flag=\n" /* flags not supported in copy from file yet */
06245          "duration=%d\n", /* Don't have any reliable way to get duration of file. */
06246 
06247          recdata->mailbox,
06248          S_OR(recdata->call_context, ""),
06249          S_OR(recdata->call_macrocontext, ""),
06250          S_OR(recdata->call_extension, ""),
06251          recdata->call_priority,
06252          S_OR(recdata->call_callerchan, "Unknown"),
06253          S_OR(recdata->call_callerid, "Unknown"),
06254          date, (long) time(NULL),
06255          S_OR(category, ""),
06256          msg_id,
06257          duration);
06258 
06259       /* Since we are recording from a file, we shouldn't need to do anything else with
06260        * this txt file */
06261       fclose(txt);
06262 
06263    } else {
06264       ast_log(LOG_WARNING, "Error opening text file for output\n");
06265       if (ast_check_realtime("voicemail_data")) {
06266          ast_destroy_realtime("voicemail_data", "filename", tmptxtfile, SENTINEL);
06267       }
06268       free_user(recipient);
06269       return -1;
06270    }
06271 
06272    /* At this point, the actual creation of a voicemail message should be finished.
06273     * Now we just need to copy the files being recorded into the receiving folder. */
06274 
06275    create_dirpath(dir, sizeof(dir), recipient->context, recipient->mailbox, recdata->folder);
06276 
06277 #ifdef IMAP_STORAGE
06278    /* make recipient info into an inboxcount friendly string */
06279    snprintf(ext_context, sizeof(ext_context), "%s@%s", recipient->mailbox, recipient->context);
06280 
06281    /* Is ext a mailbox? */
06282    /* must open stream for this user to get info! */
06283    res = inboxcount(ext_context, &newmsgs, &oldmsgs);
06284    if (res < 0) {
06285       ast_log(LOG_NOTICE, "Can not leave voicemail, unable to count messages\n");
06286       free_user(recipient);
06287       unlink(tmptxtfile);
06288       return -1;
06289    }
06290    if (!(vms = get_vm_state_by_mailbox(recipient->mailbox, recipient->context, 0))) {
06291    /* It is possible under certain circumstances that inboxcount did not
06292     * create a vm_state when it was needed. This is a catchall which will
06293     * rarely be used.
06294     */
06295       if (!(vms = create_vm_state_from_user(recipient))) {
06296          ast_log(LOG_ERROR, "Couldn't allocate necessary space\n");
06297          free_user(recipient);
06298          unlink(tmptxtfile);
06299          return -1;
06300       }
06301    }
06302    vms->newmessages++;
06303 
06304    /* here is a big difference! We add one to it later */
06305    msgnum = newmsgs + oldmsgs;
06306    ast_debug(3, "Messagecount set to %d\n", msgnum);
06307    snprintf(destination, sizeof(destination), "%simap/msg%s%04d", VM_SPOOL_DIR, recipient->mailbox, msgnum);
06308 
06309    /* Check to see if we have enough room in the mailbox. If not, spit out an error and end
06310     * Note that imap_check_limits raises inprocess_count if successful */
06311    if ((res = imap_check_limits(NULL, vms, recipient, msgnum))) {
06312       ast_log(LOG_NOTICE, "Didn't copy to voicemail. Mailbox for %s@%s is full.\n", recipient->mailbox, recipient->context);
06313       inprocess_count(recipient->mailbox, recipient->context, -1);
06314       free_user(recipient);
06315       unlink(tmptxtfile);
06316       return -1;
06317    }
06318 
06319 #else
06320 
06321    /* Check to see if the mailbox is full for ODBC/File storage */
06322    ast_debug(3, "mailbox = %d : inprocess = %d\n", count_messages(recipient, dir),
06323       inprocess_count(recipient->mailbox, recipient->context, 0));
06324    if (count_messages(recipient, dir) > recipient->maxmsg - inprocess_count(recipient->mailbox, recipient->context, +1)) {
06325       ast_log(AST_LOG_WARNING, "Didn't copy to voicemail. Mailbox for %s@%s is full.\n", recipient->mailbox, recipient->context);
06326       inprocess_count(recipient->mailbox, recipient->context, -1);
06327       free_user(recipient);
06328       unlink(tmptxtfile);
06329       return -1;
06330    }
06331 
06332    msgnum = last_message_index(recipient, dir) + 1;
06333 #endif
06334 
06335    /* Lock the directory receiving the voicemail since we want it to still exist when we attempt to copy the voicemail.
06336     * We need to unlock it before we return. */
06337    if (vm_lock_path(dir)) {
06338       ast_log(LOG_ERROR, "Couldn't lock directory %s.  Voicemail will be lost.\n", dir);
06339       /* Delete files */
06340       ast_filedelete(tmptxtfile, NULL);
06341       unlink(tmptxtfile);
06342       free_user(recipient);
06343       return -1;
06344    }
06345 
06346    make_file(destination, sizeof(destination), dir, msgnum);
06347 
06348    make_file(tmpaudiofile, sizeof(tmpaudiofile), tmpdir, msgnum);
06349 
06350    if (ast_filecopy(recdata->recording_file, tmpaudiofile, recdata->recording_ext)) {
06351       ast_log(LOG_ERROR, "Audio file failed to copy to tmp dir. Probably low disk space.\n");
06352 
06353       inprocess_count(recipient->mailbox, recipient->context, -1);
06354       ast_unlock_path(dir);
06355       free_user(recipient);
06356       unlink(tmptxtfile);
06357       return -1;
06358    }
06359 
06360    /* Alright, try to copy to the destination folder now. */
06361    if (ast_filerename(tmpaudiofile, destination, recdata->recording_ext)) {
06362       ast_log(LOG_ERROR, "Audio file failed to move to destination directory. Permissions/Overlap?\n");
06363       inprocess_count(recipient->mailbox, recipient->context, -1);
06364       ast_unlock_path(dir);
06365       free_user(recipient);
06366       unlink(tmptxtfile);
06367       return -1;
06368    }
06369 
06370    snprintf(desttxtfile, sizeof(desttxtfile), "%s.txt", destination);
06371    rename(tmptxtfile, desttxtfile);
06372 
06373    if (chmod(desttxtfile, VOICEMAIL_FILE_MODE) < 0) {
06374       ast_log(AST_LOG_ERROR, "Couldn't set permissions on voicemail text file %s: %s", desttxtfile, strerror(errno));
06375    }
06376 
06377 
06378    ast_unlock_path(dir);
06379    inprocess_count(recipient->mailbox, recipient->context, -1);
06380 
06381    /* If we copied something, we should store it either to ODBC or IMAP if we are using those. The STORE macro allows us
06382     * to do both with one line and is also safe to use with file storage mode. Also, if we are using ODBC, now is a good
06383     * time to create the voicemail database entry. */
06384    if (ast_fileexists(destination, NULL, NULL) > 0) {
06385       if (ast_check_realtime("voicemail_data")) {
06386          get_date(date, sizeof(date));
06387          ast_store_realtime("voicemail_data",
06388             "origmailbox", recdata->mailbox,
06389             "context", S_OR(recdata->context, ""),
06390             "macrocontext", S_OR(recdata->call_macrocontext, ""),
06391             "exten", S_OR(recdata->call_extension, ""),
06392             "priority", recdata->call_priority,
06393             "callerchan", S_OR(recdata->call_callerchan, "Unknown"),
06394             "callerid", S_OR(recdata->call_callerid, "Unknown"),
06395             "origdate", date,
06396             "origtime", time(NULL),
06397             "category", S_OR(category, ""),
06398             "filename", tmptxtfile,
06399             "duration", duration,
06400             SENTINEL);
06401       }
06402 
06403       STORE(dir, recipient->mailbox, recipient->context, msgnum, NULL, recipient, fmt, 0, vms, "", msg_id);
06404       notify_new_state(recipient);
06405    }
06406 
06407    free_user(recipient);
06408    unlink(tmptxtfile);
06409    return 0;
06410 }
06411 
06412 /*!
06413  * \brief Prompts the user and records a voicemail to a mailbox.
06414  * \param chan
06415  * \param ext
06416  * \param options OPT_BUSY_GREETING, OPT_UNAVAIL_GREETING
06417  * 
06418  * 
06419  * 
06420  * \return zero on success, -1 on error.
06421  */
06422 static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_options *options)
06423 {
06424 #ifdef IMAP_STORAGE
06425    int newmsgs, oldmsgs;
06426 #else
06427    char urgdir[PATH_MAX];
06428 #endif
06429    char txtfile[PATH_MAX];
06430    char tmptxtfile[PATH_MAX];
06431    struct vm_state *vms = NULL;
06432    char callerid[256];
06433    FILE *txt;
06434    char date[256];
06435    int txtdes;
06436    int res = 0;
06437    int msgnum;
06438    int duration = 0;
06439    int sound_duration = 0;
06440    int ausemacro = 0;
06441    int ousemacro = 0;
06442    int ouseexten = 0;
06443    char tmpdur[16];
06444    char priority[16];
06445    char origtime[16];
06446    char dir[PATH_MAX];
06447    char tmpdir[PATH_MAX];
06448    char fn[PATH_MAX];
06449    char prefile[PATH_MAX] = "";
06450    char tempfile[PATH_MAX] = "";
06451    char ext_context[256] = "";
06452    char fmt[80];
06453    char *context;
06454    char ecodes[17] = "#";
06455    struct ast_str *tmp = ast_str_create(16);
06456    char *tmpptr;
06457    struct ast_vm_user *vmu;
06458    struct ast_vm_user svm;
06459    const char *category = NULL;
06460    const char *code;
06461    const char *alldtmf = "0123456789ABCD*#";
06462    char flag[80];
06463 
06464    if (!tmp) {
06465       return -1;
06466    }
06467 
06468    ast_str_set(&tmp, 0, "%s", ext);
06469    ext = ast_str_buffer(tmp);
06470    if ((context = strchr(ext, '@'))) {
06471       *context++ = '\0';
06472       tmpptr = strchr(context, '&');
06473    } else {
06474       tmpptr = strchr(ext, '&');
06475    }
06476 
06477    if (tmpptr)
06478       *tmpptr++ = '\0';
06479 
06480    ast_channel_lock(chan);
06481    if ((category = pbx_builtin_getvar_helper(chan, "VM_CATEGORY"))) {
06482       category = ast_strdupa(category);
06483    }
06484    ast_channel_unlock(chan);
06485 
06486    if (ast_test_flag(options, OPT_MESSAGE_Urgent)) {
06487       ast_copy_string(flag, "Urgent", sizeof(flag));
06488    } else if (ast_test_flag(options, OPT_MESSAGE_PRIORITY)) {
06489       ast_copy_string(flag, "PRIORITY", sizeof(flag));
06490    } else {
06491       flag[0] = '\0';
06492    }
06493 
06494    ast_debug(3, "Before find_user\n");
06495    if (!(vmu = find_user(&svm, context, ext))) {
06496       ast_log(AST_LOG_WARNING, "No entry in voicemail config file for '%s'\n", ext);
06497       pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
06498       ast_free(tmp);
06499       return res;
06500    }
06501    /* Setup pre-file if appropriate */
06502    if (strcmp(vmu->context, "default"))
06503       snprintf(ext_context, sizeof(ext_context), "%s@%s", ext, vmu->context);
06504    else
06505       ast_copy_string(ext_context, vmu->mailbox, sizeof(ext_context));
06506 
06507    /* Set the path to the prefile. Will be one of 
06508       VM_SPOOL_DIRcontext/ext/busy
06509       VM_SPOOL_DIRcontext/ext/unavail
06510       Depending on the flag set in options.
06511    */
06512    if (ast_test_flag(options, OPT_BUSY_GREETING)) {
06513       snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, ext);
06514    } else if (ast_test_flag(options, OPT_UNAVAIL_GREETING)) {
06515       snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, ext);
06516    }
06517    /* Set the path to the tmpfile as
06518       VM_SPOOL_DIR/context/ext/temp
06519       and attempt to create the folder structure.
06520    */
06521    snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", VM_SPOOL_DIR, vmu->context, ext);
06522    if ((res = create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, ext, "tmp"))) {
06523       ast_log(AST_LOG_WARNING, "Failed to make directory (%s)\n", tempfile);
06524       ast_free(tmp);
06525       return -1;
06526    }
06527    RETRIEVE(tempfile, -1, vmu->mailbox, vmu->context);
06528    if (ast_fileexists(tempfile, NULL, NULL) > 0)
06529       ast_copy_string(prefile, tempfile, sizeof(prefile));
06530 
06531    DISPOSE(tempfile, -1);
06532    /* It's easier just to try to make it than to check for its existence */
06533 #ifndef IMAP_STORAGE
06534    create_dirpath(dir, sizeof(dir), vmu->context, ext, "INBOX");
06535 #else
06536    snprintf(dir, sizeof(dir), "%simap", VM_SPOOL_DIR);
06537    if (mkdir(dir, VOICEMAIL_DIR_MODE) && errno != EEXIST) {
06538       ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dir, strerror(errno));
06539    }
06540 #endif
06541 
06542    /* Check current or macro-calling context for special extensions */
06543    if (ast_test_flag(vmu, VM_OPERATOR)) {
06544       if (!ast_strlen_zero(vmu->exit)) {
06545          if (ast_exists_extension(chan, vmu->exit, "o", 1,
06546             S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
06547             strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
06548             ouseexten = 1;
06549          }
06550       } else if (ast_exists_extension(chan, ast_channel_context(chan), "o", 1,
06551          S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
06552          strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
06553          ouseexten = 1;
06554       } else if (!ast_strlen_zero(ast_channel_macrocontext(chan))
06555          && ast_exists_extension(chan, ast_channel_macrocontext(chan), "o", 1,
06556             S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
06557          strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
06558          ousemacro = 1;
06559       }
06560    }
06561 
06562    if (!ast_strlen_zero(vmu->exit)) {
06563       if (ast_exists_extension(chan, vmu->exit, "a", 1,
06564          S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
06565          strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
06566       }
06567    } else if (ast_exists_extension(chan, ast_channel_context(chan), "a", 1,
06568       S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
06569       strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
06570    } else if (!ast_strlen_zero(ast_channel_macrocontext(chan))
06571       && ast_exists_extension(chan, ast_channel_macrocontext(chan), "a", 1,
06572          S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
06573       strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
06574       ausemacro = 1;
06575    }
06576 
06577    if (ast_test_flag(options, OPT_DTMFEXIT)) {
06578       for (code = alldtmf; *code; code++) {
06579          char e[2] = "";
06580          e[0] = *code;
06581          if (strchr(ecodes, e[0]) == NULL
06582             && ast_canmatch_extension(chan,
06583                (!ast_strlen_zero(options->exitcontext) ? options->exitcontext : ast_channel_context(chan)),
06584                e, 1, S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
06585             strncat(ecodes, e, sizeof(ecodes) - strlen(ecodes) - 1);
06586          }
06587       }
06588    }
06589 
06590    /* Play the beginning intro if desired */
06591    if (!ast_strlen_zero(prefile)) {
06592 #ifdef ODBC_STORAGE
06593       int success = 
06594 #endif
06595          RETRIEVE(prefile, -1, ext, context);
06596       if (ast_fileexists(prefile, NULL, NULL) > 0) {
06597          if (ast_streamfile(chan, prefile, ast_channel_language(chan)) > -1) 
06598             res = ast_waitstream(chan, ecodes);
06599 #ifdef ODBC_STORAGE
06600          if (success == -1) {
06601             /* We couldn't retrieve the file from the database, but we found it on the file system. Let's put it in the database. */
06602             ast_debug(1, "Greeting not retrieved from database, but found in file storage. Inserting into database\n");
06603             store_file(prefile, vmu->mailbox, vmu->context, -1);
06604          }
06605 #endif
06606       } else {
06607          ast_debug(1, "%s doesn't exist, doing what we can\n", prefile);
06608          res = invent_message(chan, vmu->context, ext, ast_test_flag(options, OPT_BUSY_GREETING), ecodes);
06609       }
06610       DISPOSE(prefile, -1);
06611       if (res < 0) {
06612          ast_debug(1, "Hang up during prefile playback\n");
06613          free_user(vmu);
06614          pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
06615          ast_free(tmp);
06616          return -1;
06617       }
06618    }
06619    if (res == '#') {
06620       /* On a '#' we skip the instructions */
06621       ast_set_flag(options, OPT_SILENT);
06622       res = 0;
06623    }
06624    /* If maxmsg is zero, act as a "greetings only" voicemail: Exit successfully without recording */
06625    if (vmu->maxmsg == 0) {
06626       ast_debug(3, "Greetings only VM (maxmsg=0), Skipping voicemail recording\n");
06627       pbx_builtin_setvar_helper(chan, "VMSTATUS", "SUCCESS");
06628       goto leave_vm_out;
06629    }
06630    if (!res && !ast_test_flag(options, OPT_SILENT)) {
06631       res = ast_stream_and_wait(chan, INTRO, ecodes);
06632       if (res == '#') {
06633          ast_set_flag(options, OPT_SILENT);
06634          res = 0;
06635       }
06636    }
06637    if (res > 0)
06638       ast_stopstream(chan);
06639    /* Check for a '*' here in case the caller wants to escape from voicemail to something
06640     other than the operator -- an automated attendant or mailbox login for example */
06641    if (res == '*') {
06642       ast_channel_exten_set(chan, "a");
06643       if (!ast_strlen_zero(vmu->exit)) {
06644          ast_channel_context_set(chan, vmu->exit);
06645       } else if (ausemacro && !ast_strlen_zero(ast_channel_macrocontext(chan))) {
06646          ast_channel_context_set(chan, ast_channel_macrocontext(chan));
06647       }
06648       ast_channel_priority_set(chan, 0);
06649       free_user(vmu);
06650       pbx_builtin_setvar_helper(chan, "VMSTATUS", "USEREXIT");
06651       ast_free(tmp);
06652       return 0;
06653    }
06654 
06655    /* Check for a '0' here */
06656    if (ast_test_flag(vmu, VM_OPERATOR) && res == '0') {
06657    transfer:
06658       if (ouseexten || ousemacro) {
06659          ast_channel_exten_set(chan, "o");
06660          if (!ast_strlen_zero(vmu->exit)) {
06661             ast_channel_context_set(chan, vmu->exit);
06662          } else if (ousemacro && !ast_strlen_zero(ast_channel_macrocontext(chan))) {
06663             ast_channel_context_set(chan, ast_channel_macrocontext(chan));
06664          }
06665          ast_play_and_wait(chan, "transfer");
06666          ast_channel_priority_set(chan, 0);
06667          free_user(vmu);
06668          pbx_builtin_setvar_helper(chan, "VMSTATUS", "USEREXIT");
06669       }
06670       ast_free(tmp);
06671       return OPERATOR_EXIT;
06672    }
06673 
06674    /* Allow all other digits to exit Voicemail and return to the dialplan */
06675    if (ast_test_flag(options, OPT_DTMFEXIT) && res > 0) {
06676       if (!ast_strlen_zero(options->exitcontext)) {
06677          ast_channel_context_set(chan, options->exitcontext);
06678       }
06679       free_user(vmu);
06680       ast_free(tmp);
06681       pbx_builtin_setvar_helper(chan, "VMSTATUS", "USEREXIT");
06682       return res;
06683    }
06684 
06685    if (res < 0) {
06686       free_user(vmu);
06687       pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
06688       ast_free(tmp);
06689       return -1;
06690    }
06691    /* The meat of recording the message...  All the announcements and beeps have been played*/
06692    ast_copy_string(fmt, vmfmts, sizeof(fmt));
06693    if (!ast_strlen_zero(fmt)) {
06694       char msg_id[MSG_ID_LEN] = "";
06695       msgnum = 0;
06696 
06697 #ifdef IMAP_STORAGE
06698       /* Is ext a mailbox? */
06699       /* must open stream for this user to get info! */
06700       res = inboxcount(ext_context, &newmsgs, &oldmsgs);
06701       if (res < 0) {
06702          ast_log(AST_LOG_NOTICE, "Can not leave voicemail, unable to count messages\n");
06703          ast_free(tmp);
06704          return -1;
06705       }
06706       if (!(vms = get_vm_state_by_mailbox(ext, context, 0))) {
06707       /* It is possible under certain circumstances that inboxcount did not
06708        * create a vm_state when it was needed. This is a catchall which will
06709        * rarely be used.
06710        */
06711          if (!(vms = create_vm_state_from_user(vmu))) {
06712             ast_log(AST_LOG_ERROR, "Couldn't allocate necessary space\n");
06713             ast_free(tmp);
06714             return -1;
06715          }
06716       }
06717       vms->newmessages++;
06718 
06719       /* here is a big difference! We add one to it later */
06720       msgnum = newmsgs + oldmsgs;
06721       ast_debug(3, "Messagecount set to %d\n", msgnum);
06722       snprintf(fn, sizeof(fn), "%simap/msg%s%04d", VM_SPOOL_DIR, vmu->mailbox, msgnum);
06723       /* set variable for compatibility */
06724       pbx_builtin_setvar_helper(chan, "VM_MESSAGEFILE", "IMAP_STORAGE");
06725 
06726       if ((res = imap_check_limits(chan, vms, vmu, msgnum))) {
06727          goto leave_vm_out;
06728       }
06729 #else
06730       if (count_messages(vmu, dir) >= vmu->maxmsg - inprocess_count(vmu->mailbox, vmu->context, +1)) {
06731          res = ast_streamfile(chan, "vm-mailboxfull", ast_channel_language(chan));
06732          if (!res)
06733             res = ast_waitstream(chan, "");
06734          ast_log(AST_LOG_WARNING, "No more messages possible\n");
06735          pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
06736          inprocess_count(vmu->mailbox, vmu->context, -1);
06737          goto leave_vm_out;
06738       }
06739 
06740 #endif
06741       snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir);
06742       txtdes = mkstemp(tmptxtfile);
06743       chmod(tmptxtfile, VOICEMAIL_FILE_MODE & ~my_umask);
06744       if (txtdes < 0) {
06745          res = ast_streamfile(chan, "vm-mailboxfull", ast_channel_language(chan));
06746          if (!res)
06747             res = ast_waitstream(chan, "");
06748          ast_log(AST_LOG_ERROR, "Unable to create message file: %s\n", strerror(errno));
06749          pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
06750          inprocess_count(vmu->mailbox, vmu->context, -1);
06751          goto leave_vm_out;
06752       }
06753 
06754       /* Now play the beep once we have the message number for our next message. */
06755       if (res >= 0) {
06756          /* Unless we're *really* silent, try to send the beep */
06757          res = ast_stream_and_wait(chan, "beep", "");
06758       }
06759             
06760       /* Store information in real-time storage */
06761       if (ast_check_realtime("voicemail_data")) {
06762          snprintf(priority, sizeof(priority), "%d", ast_channel_priority(chan));
06763          snprintf(origtime, sizeof(origtime), "%ld", (long) time(NULL));
06764          get_date(date, sizeof(date));
06765          ast_callerid_merge(callerid, sizeof(callerid),
06766             S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL),
06767             S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
06768             "Unknown");
06769          ast_store_realtime("voicemail_data",
06770             "origmailbox", ext,
06771             "context", ast_channel_context(chan),
06772             "macrocontext", ast_channel_macrocontext(chan),
06773             "exten", ast_channel_exten(chan),
06774             "priority", priority,
06775             "callerchan", ast_channel_name(chan),
06776             "callerid", callerid,
06777             "origdate", date,
06778             "origtime", origtime,
06779             "category", S_OR(category, ""),
06780             "filename", tmptxtfile,
06781             SENTINEL);
06782       }
06783 
06784       /* Store information */
06785       txt = fdopen(txtdes, "w+");
06786       if (txt) {
06787          generate_msg_id(msg_id);
06788          get_date(date, sizeof(date));
06789          ast_callerid_merge(callerid, sizeof(callerid),
06790             S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL),
06791             S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
06792             "Unknown");
06793          fprintf(txt, 
06794             ";\n"
06795             "; Message Information file\n"
06796             ";\n"
06797             "[message]\n"
06798             "origmailbox=%s\n"
06799             "context=%s\n"
06800             "macrocontext=%s\n"
06801             "exten=%s\n"
06802             "rdnis=%s\n"
06803             "priority=%d\n"
06804             "callerchan=%s\n"
06805             "callerid=%s\n"
06806             "origdate=%s\n"
06807             "origtime=%ld\n"
06808             "category=%s\n"
06809             "msg_id=%s\n",
06810             ext,
06811             ast_channel_context(chan),
06812             ast_channel_macrocontext(chan), 
06813             ast_channel_exten(chan),
06814             S_COR(ast_channel_redirecting(chan)->from.number.valid,
06815                ast_channel_redirecting(chan)->from.number.str, "unknown"),
06816             ast_channel_priority(chan),
06817             ast_channel_name(chan),
06818             callerid,
06819             date, (long) time(NULL),
06820             category ? category : "",
06821             msg_id);
06822       } else {
06823          ast_log(AST_LOG_WARNING, "Error opening text file for output\n");
06824          inprocess_count(vmu->mailbox, vmu->context, -1);
06825          if (ast_check_realtime("voicemail_data")) {
06826             ast_destroy_realtime("voicemail_data", "filename", tmptxtfile, SENTINEL);
06827          }
06828          res = ast_streamfile(chan, "vm-mailboxfull", ast_channel_language(chan));
06829          goto leave_vm_out;
06830       }
06831       res = play_record_review(chan, NULL, tmptxtfile, vmu->maxsecs, fmt, 1, vmu, &duration, &sound_duration, NULL, options->record_gain, vms, flag, msg_id);
06832 
06833       if (txt) {
06834          fprintf(txt, "flag=%s\n", flag);
06835          if (sound_duration < vmu->minsecs) {
06836             fclose(txt);
06837             ast_verb(3, "Recording was %d seconds long but needs to be at least %d - abandoning\n", sound_duration, vmu->minsecs);
06838             ast_filedelete(tmptxtfile, NULL);
06839             unlink(tmptxtfile);
06840             if (ast_check_realtime("voicemail_data")) {
06841                ast_destroy_realtime("voicemail_data", "filename", tmptxtfile, SENTINEL);
06842             }
06843             inprocess_count(vmu->mailbox, vmu->context, -1);
06844          } else {
06845             fprintf(txt, "duration=%d\n", duration);
06846             fclose(txt);
06847             if (vm_lock_path(dir)) {
06848                ast_log(AST_LOG_ERROR, "Couldn't lock directory %s.  Voicemail will be lost.\n", dir);
06849                /* Delete files */
06850                ast_filedelete(tmptxtfile, NULL);
06851                unlink(tmptxtfile);
06852                inprocess_count(vmu->mailbox, vmu->context, -1);
06853             } else if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
06854                ast_debug(1, "The recorded media file is gone, so we should remove the .txt file too!\n");
06855                unlink(tmptxtfile);
06856                ast_unlock_path(dir);
06857                inprocess_count(vmu->mailbox, vmu->context, -1);
06858                if (ast_check_realtime("voicemail_data")) {
06859                   ast_destroy_realtime("voicemail_data", "filename", tmptxtfile, SENTINEL);
06860                }
06861             } else {
06862 #ifndef IMAP_STORAGE
06863                msgnum = last_message_index(vmu, dir) + 1;
06864 #endif
06865                make_file(fn, sizeof(fn), dir, msgnum);
06866 
06867                /* assign a variable with the name of the voicemail file */ 
06868 #ifndef IMAP_STORAGE
06869                pbx_builtin_setvar_helper(chan, "VM_MESSAGEFILE", fn);
06870 #else
06871                pbx_builtin_setvar_helper(chan, "VM_MESSAGEFILE", "IMAP_STORAGE");
06872 #endif
06873 
06874                snprintf(txtfile, sizeof(txtfile), "%s.txt", fn);
06875                ast_filerename(tmptxtfile, fn, NULL);
06876                rename(tmptxtfile, txtfile);
06877                inprocess_count(vmu->mailbox, vmu->context, -1);
06878 
06879                /* Properly set permissions on voicemail text descriptor file.
06880                   Unfortunately mkstemp() makes this file 0600 on most unix systems. */
06881                if (chmod(txtfile, VOICEMAIL_FILE_MODE) < 0)
06882                   ast_log(AST_LOG_ERROR, "Couldn't set permissions on voicemail text file %s: %s", txtfile, strerror(errno));
06883 
06884                ast_unlock_path(dir);
06885                if (ast_check_realtime("voicemail_data")) {
06886                   snprintf(tmpdur, sizeof(tmpdur), "%d", duration);
06887                   ast_update_realtime("voicemail_data", "filename", tmptxtfile, "filename", fn, "duration", tmpdur, SENTINEL);
06888                }
06889                /* We must store the file first, before copying the message, because
06890                 * ODBC storage does the entire copy with SQL.
06891                 */
06892                if (ast_fileexists(fn, NULL, NULL) > 0) {
06893                   STORE(dir, vmu->mailbox, vmu->context, msgnum, chan, vmu, fmt, duration, vms, flag, msg_id);
06894                }
06895 
06896                /* Are there to be more recipients of this message? */
06897                while (tmpptr) {
06898                   struct ast_vm_user recipu, *recip;
06899                   char *exten, *cntx;
06900 
06901                   exten = strsep(&tmpptr, "&");
06902                   cntx = strchr(exten, '@');
06903                   if (cntx) {
06904                      *cntx = '\0';
06905                      cntx++;
06906                   }
06907                   if ((recip = find_user(&recipu, cntx, exten))) {
06908                      copy_message(chan, vmu, 0, msgnum, duration, recip, fmt, dir, flag, NULL);
06909                      free_user(recip);
06910                   }
06911                }
06912 #ifndef IMAP_STORAGE
06913                if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) { /* If this is an Urgent message */
06914                   /* Move the message from INBOX to Urgent folder if this is urgent! */
06915                   char sfn[PATH_MAX];
06916                   char dfn[PATH_MAX];
06917                   int x;
06918                   /* It's easier just to try to make it than to check for its existence */
06919                   create_dirpath(urgdir, sizeof(urgdir), vmu->context, ext, "Urgent");
06920                   x = last_message_index(vmu, urgdir) + 1;
06921                   make_file(sfn, sizeof(sfn), dir, msgnum);
06922                   make_file(dfn, sizeof(dfn), urgdir, x);
06923                   ast_debug(5, "Created an Urgent message, moving file from %s to %s.\n", sfn, dfn);
06924                   RENAME(dir, msgnum, vmu->mailbox, vmu->context, urgdir, x, sfn, dfn);
06925                   /* Notification must happen for this new message in Urgent folder, not INBOX */
06926                   ast_copy_string(fn, dfn, sizeof(fn));
06927                   msgnum = x;
06928                }
06929 #endif
06930                /* Notification needs to happen after the copy, though. */
06931                if (ast_fileexists(fn, NULL, NULL)) {
06932 #ifdef IMAP_STORAGE
06933                   notify_new_message(chan, vmu, vms, msgnum, duration, fmt,
06934                      S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
06935                      S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL),
06936                      flag);
06937 #else
06938                   notify_new_message(chan, vmu, NULL, msgnum, duration, fmt,
06939                      S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
06940                      S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL),
06941                      flag);
06942 #endif
06943                }
06944 
06945                /* Disposal needs to happen after the optional move and copy */
06946                if (ast_fileexists(fn, NULL, NULL)) {
06947                   DISPOSE(dir, msgnum);
06948                }
06949             }
06950          }
06951       } else {
06952          inprocess_count(vmu->mailbox, vmu->context, -1);
06953       }
06954       if (res == '0') {
06955          goto transfer;
06956       } else if (res > 0 && res != 't')
06957          res = 0;
06958 
06959       if (sound_duration < vmu->minsecs)
06960          /* XXX We should really give a prompt too short/option start again, with leave_vm_out called only after a timeout XXX */
06961          pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
06962       else
06963          pbx_builtin_setvar_helper(chan, "VMSTATUS", "SUCCESS");
06964    } else
06965       ast_log(AST_LOG_WARNING, "No format for saving voicemail?\n");
06966 leave_vm_out:
06967    free_user(vmu);
06968 
06969 #ifdef IMAP_STORAGE
06970    /* expunge message - use UID Expunge if supported on IMAP server*/
06971    ast_debug(3, "*** Checking if we can expunge, expungeonhangup set to %d\n", expungeonhangup);
06972    if (expungeonhangup == 1) {
06973       ast_mutex_lock(&vms->lock);
06974 #ifdef HAVE_IMAP_TK2006
06975       if (LEVELUIDPLUS (vms->mailstream)) {
06976          mail_expunge_full(vms->mailstream, NIL, EX_UID);
06977       } else 
06978 #endif
06979          mail_expunge(vms->mailstream);
06980       ast_mutex_unlock(&vms->lock);
06981    }
06982 #endif
06983 
06984    ast_free(tmp);
06985    return res;
06986 }
06987 
06988 #if !defined(IMAP_STORAGE)
06989 static int resequence_mailbox(struct ast_vm_user *vmu, char *dir, int stopcount)
06990 {
06991    /* we know the actual number of messages, so stop process when number is hit */
06992 
06993    int x, dest;
06994    char sfn[PATH_MAX];
06995    char dfn[PATH_MAX];
06996 
06997    if (vm_lock_path(dir)) {
06998       return ERROR_LOCK_PATH;
06999    }
07000 
07001    for (x = 0, dest = 0; dest != stopcount && x < vmu->maxmsg + 10; x++) {
07002       make_file(sfn, sizeof(sfn), dir, x);
07003       if (EXISTS(dir, x, sfn, NULL)) {
07004 
07005          if (x != dest) {
07006             make_file(dfn, sizeof(dfn), dir, dest);
07007             RENAME(dir, x, vmu->mailbox, vmu->context, dir, dest, sfn, dfn);
07008          }
07009 
07010          dest++;
07011       }
07012    }
07013    ast_unlock_path(dir);
07014 
07015    return dest;
07016 }
07017 #endif
07018 
07019 static int say_and_wait(struct ast_channel *chan, int num, const char *language)
07020 {
07021    int d;
07022    d = ast_say_number(chan, num, AST_DIGIT_ANY, language, NULL);
07023    return d;
07024 }
07025 
07026 static int save_to_folder(struct ast_vm_user *vmu, struct vm_state *vms, int msg, int box, int *newmsg, int move)
07027 {
07028 #ifdef IMAP_STORAGE
07029    /* we must use mbox(x) folder names, and copy the message there */
07030    /* simple. huh? */
07031    char sequence[10];
07032    char mailbox[256];
07033    int res;
07034 
07035    /* get the real IMAP message number for this message */
07036    snprintf(sequence, sizeof(sequence), "%ld", vms->msgArray[msg]);
07037 
07038    ast_debug(3, "Copying sequence %s to mailbox %s\n", sequence, mbox(vmu, box));
07039    ast_mutex_lock(&vms->lock);
07040    /* if save to Old folder, put in INBOX as read */
07041    if (box == OLD_FOLDER) {
07042       mail_setflag(vms->mailstream, sequence, "\\Seen");
07043       mail_clearflag(vms->mailstream, sequence, "\\Unseen");
07044    } else if (box == NEW_FOLDER) {
07045       mail_setflag(vms->mailstream, sequence, "\\Unseen");
07046       mail_clearflag(vms->mailstream, sequence, "\\Seen");
07047    }
07048    if (!strcasecmp(mbox(vmu, NEW_FOLDER), vms->curbox) && (box == NEW_FOLDER || box == OLD_FOLDER)) {
07049       ast_mutex_unlock(&vms->lock);
07050       return 0;
07051    }
07052    /* Create the folder if it don't exist */
07053    imap_mailbox_name(mailbox, sizeof(mailbox), vms, box, 1); /* Get the full mailbox name */
07054    ast_debug(5, "Checking if folder exists: %s\n", mailbox);
07055    if (mail_create(vms->mailstream, mailbox) == NIL)
07056       ast_debug(5, "Folder exists.\n");
07057    else
07058       ast_log(AST_LOG_NOTICE, "Folder %s created!\n", mbox(vmu, box));
07059    if (move) {
07060       res = !mail_move(vms->mailstream, sequence, (char *) mbox(vmu, box));
07061    } else {
07062       res = !mail_copy(vms->mailstream, sequence, (char *) mbox(vmu, box));
07063    }
07064    ast_mutex_unlock(&vms->lock);
07065    return res;
07066 #else
07067    char *dir = vms->curdir;
07068    char *username = vms->username;
07069    char *context = vmu->context;
07070    char sfn[PATH_MAX];
07071    char dfn[PATH_MAX];
07072    char ddir[PATH_MAX];
07073    const char *dbox = mbox(vmu, box);
07074    int x, i;
07075    create_dirpath(ddir, sizeof(ddir), context, username, dbox);
07076 
07077    if (vm_lock_path(ddir))
07078       return ERROR_LOCK_PATH;
07079 
07080    x = last_message_index(vmu, ddir) + 1;
07081 
07082    if (box == 10 && x >= vmu->maxdeletedmsg) { /* "Deleted" folder*/
07083       x--;
07084       for (i = 1; i <= x; i++) {
07085          /* Push files down a "slot".  The oldest file (msg0000) will be deleted. */
07086          make_file(sfn, sizeof(sfn), ddir, i);
07087          make_file(dfn, sizeof(dfn), ddir, i - 1);
07088          if (EXISTS(ddir, i, sfn, NULL)) {
07089             RENAME(ddir, i, vmu->mailbox, vmu->context, ddir, i - 1, sfn, dfn);
07090          } else
07091             break;
07092       }
07093    } else {
07094       if (x >= vmu->maxmsg) {
07095          ast_unlock_path(ddir);
07096          return -1;
07097       }
07098    }
07099    make_file(sfn, sizeof(sfn), dir, msg);
07100    make_file(dfn, sizeof(dfn), ddir, x);
07101    if (strcmp(sfn, dfn)) {
07102       COPY(dir, msg, ddir, x, username, context, sfn, dfn);