Merge ast_str_opaque branch (discontinue usage of ast_str internals)
[asterisk/asterisk.git] / apps / app_voicemail.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2006, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Comedian Mail - Voicemail System
22  *
23  * \author Mark Spencer <markster@digium.com>
24  *
25  * \extref Unixodbc - http://www.unixodbc.org
26  * \extref A source distribution of University of Washington's IMAP
27 c-client (http://www.washington.edu/imap/
28  * 
29  * \par See also
30  * \arg \ref Config_vm
31  * \note For information about voicemail IMAP storage, read doc/imapstorage.txt
32  * \ingroup applications
33  * \note This module requires res_adsi to load. This needs to be optional
34  * during compilation.
35  *
36  *
37  *
38  * \note  This file is now almost impossible to work with, due to all \#ifdefs.
39  *        Feels like the database code before realtime. Someone - please come up
40  *        with a plan to clean this up.
41  */
42
43 /*** MODULEINFO
44         <depend>res_smdi</depend>
45  ***/
46
47 /*** MAKEOPTS
48 <category name="MENUSELECT_OPTS_app_voicemail" displayname="Voicemail Build Options" positive_output="yes" remove_on_change="apps/app_voicemail.o apps/app_directory.o">
49         <member name="ODBC_STORAGE" displayname="Storage of Voicemail using ODBC">
50                 <depend>odbc</depend>
51                 <depend>ltdl</depend>
52                 <use>unixodbc</use>
53                 <use>iodbc</use>
54                 <conflict>IMAP_STORAGE</conflict>
55                 <defaultenabled>no</defaultenabled>
56         </member>
57         <member name="IMAP_STORAGE" displayname="Storage of Voicemail using IMAP4">
58                 <depend>imap_tk</depend>
59                 <conflict>ODBC_STORAGE</conflict>
60                 <use>openssl</use>
61                 <defaultenabled>no</defaultenabled>
62         </member>
63 </category>
64  ***/
65
66 #include "asterisk.h"
67
68 #ifdef IMAP_STORAGE
69 #include <ctype.h>
70 #include <signal.h>
71 #include <pwd.h>
72 #ifdef USE_SYSTEM_IMAP
73 #include <imap/c-client.h>
74 #include <imap/imap4r1.h>
75 #include <imap/linkage.h>
76 #elif defined (USE_SYSTEM_CCLIENT)
77 #include <c-client/c-client.h>
78 #include <c-client/imap4r1.h>
79 #include <c-client/linkage.h>
80 #else
81 #include "c-client.h"
82 #include "imap4r1.h"
83 #include "linkage.h"
84 #endif
85 #endif
86
87 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
88
89 #include "asterisk/paths.h"     /* use ast_config_AST_SPOOL_DIR */
90 #include <sys/time.h>
91 #include <sys/stat.h>
92 #include <sys/mman.h>
93 #include <time.h>
94 #include <dirent.h>
95
96 #include "asterisk/logger.h"
97 #include "asterisk/lock.h"
98 #include "asterisk/file.h"
99 #include "asterisk/channel.h"
100 #include "asterisk/pbx.h"
101 #include "asterisk/config.h"
102 #include "asterisk/say.h"
103 #include "asterisk/module.h"
104 #include "asterisk/adsi.h"
105 #include "asterisk/app.h"
106 #include "asterisk/manager.h"
107 #include "asterisk/dsp.h"
108 #include "asterisk/localtime.h"
109 #include "asterisk/cli.h"
110 #include "asterisk/utils.h"
111 #include "asterisk/stringfields.h"
112 #include "asterisk/smdi.h"
113 #include "asterisk/event.h"
114 #include "asterisk/taskprocessor.h"
115
116 #ifdef ODBC_STORAGE
117 #include "asterisk/res_odbc.h"
118 #endif
119
120 /*** DOCUMENTATION
121         <application name="VoiceMail" language="en_US">
122                 <synopsis>
123                         Leave a Voicemail message.
124                 </synopsis>
125                 <syntax>
126                         <parameter name="mailboxs" argsep="&amp;" required="true">
127                                 <argument name="mailbox1" argsep="@" required="true">
128                                         <argument name="mailbox" required="true" />
129                                         <argument name="context" />
130                                 </argument>
131                                 <argument name="mailbox2" argsep="@" multiple="true">
132                                         <argument name="mailbox" required="true" />
133                                         <argument name="context" />
134                                 </argument>
135                         </parameter>
136                         <parameter name="options">
137                                 <optionlist>
138                                         <option name="b">
139                                                 <para>Play the <literal>busy</literal> greeting to the calling party.</para>
140                                         </option>
141                                         <option name="d">
142                                                 <argument name="c" />
143                                                 <para>Accept digits for a new extension in context <replaceable>c</replaceable>,
144                                                 if played during the greeting. Context defaults to the current context.</para>
145                                         </option>
146                                         <option name="g">
147                                                 <argument name="#" required="true" />
148                                                 <para>Use the specified amount of gain when recording the voicemail
149                                                 message. The units are whole-number decibels (dB). Only works on supported
150                                                 technologies, which is DAHDI only.</para>
151                                         </option>
152                                         <option name="s">
153                                                 <para>Skip the playback of instructions for leaving a message to the
154                                                 calling party.</para>
155                                         </option>
156                                         <option name="u">
157                                                 <para>Play the <literal>unavailable</literal> greeting.</para>
158                                         </option>
159                                         <option name="U">
160                                                 <para>Mark message as <literal>URGENT</literal>.</para>
161                                         </option>
162                                         <option name="P">
163                                                 <para>Mark message as <literal>PRIORITY</literal>.</para>
164                                         </option>
165                                 </optionlist>
166                         </parameter>
167                 </syntax>
168                 <description>
169                         <para>This application allows the calling party to leave a message for the specified
170                         list of mailboxes. When multiple mailboxes are specified, the greeting will be taken from
171                         the first mailbox specified. Dialplan execution will stop if the specified mailbox does not
172                         exist.</para>
173                         <para>The Voicemail application will exit if any of the following DTMF digits are received:</para>
174                         <enumlist>
175                                 <enum name="0">
176                                         <para>Jump to the <literal>o</literal> extension in the current dialplan context.</para>
177                                 </enum>
178                                 <enum name="*">
179                                         <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
180                                 </enum>
181                         </enumlist>
182                         <para>This application will set the following channel variable upon completion:</para>
183                         <variablelist>
184                                 <variable name="VMSTATUS">
185                                         <para>This indicates the status of the execution of the VoiceMail application.</para>
186                                         <value name="SUCCESS" />
187                                         <value name="USEREXIT" />
188                                         <value name="FAILED" />
189                                 </variable>
190                         </variablelist>
191                 </description>
192         </application>
193         <application name="VoiceMailMain" language="en_US">
194                 <synopsis>
195                         Check Voicemail messages.
196                 </synopsis>
197                 <syntax>
198                         <parameter name="mailbox" required="true" argsep="@">
199                                 <argument name="mailbox" />
200                                 <argument name="context" />
201                         </parameter>
202                         <parameter name="options">
203                                 <optionlist>
204                                         <option name="p">
205                                                 <para>Consider the <replaceable>mailbox</replaceable> parameter as a prefix to
206                                                 the mailbox that is entered by the caller.</para>
207                                         </option>
208                                         <option name="g">
209                                                 <argument name="#" required="true" />
210                                                 <para>Use the specified amount of gain when recording a voicemail message.
211                                                 The units are whole-number decibels (dB).</para>
212                                         </option>
213                                         <option name="s">
214                                                 <para>Skip checking the passcode for the mailbox.</para>
215                                         </option>
216                                         <option name="a">
217                                                 <argument name="folder" required="true" />
218                                                 <para>Skip folder prompt and go directly to <replaceable>folder</replaceable> specified.
219                                                 Defaults to <literal>INBOX</literal>.</para>
220                                         </option>
221                                 </optionlist>
222                         </parameter>
223                 </syntax>
224                 <description>
225                         <para>This application allows the calling party to check voicemail messages. A specific
226                         <replaceable>mailbox</replaceable>, and optional corresponding <replaceable>context</replaceable>,
227                         may be specified. If a <replaceable>mailbox</replaceable> is not provided, the calling party will
228                         be prompted to enter one. If a <replaceable>context</replaceable> is not specified, the
229                         <literal>default</literal> context will be used.</para>
230                 </description>
231         </application>
232         <application name="MailboxExists" language="en_US">
233                 <synopsis>
234                         Check to see if Voicemail mailbox exists.
235                 </synopsis>
236                 <syntax>
237                         <parameter name="mailbox" required="true" argsep="@">
238                                 <argument name="mailbox" required="true" />
239                                 <argument name="context" />
240                         </parameter>
241                         <parameter name="options">
242                                 <para>None options.</para>
243                         </parameter>
244                 </syntax>
245                 <description>
246                         <para>Check to see if the specified <replaceable>mailbox</replaceable> exists. If no voicemail
247                         <replaceable>context</replaceable> is specified, the <literal>default</literal> context
248                         will be used.</para>
249                         <para>This application will set the following channel variable upon completion:</para>
250                         <variablelist>
251                                 <variable name="VMBOXEXISTSSTATUS">
252                                         <para>This will contain the status of the execution of the MailboxExists application.
253                                         Possible values include:</para>
254                                         <value name="SUCCESS" />
255                                         <value name="FAILED" />
256                                 </variable>
257                         </variablelist>
258                 </description>
259         </application>
260         <application name="VMAuthenticate" language="en_US">
261                 <synopsis>
262                         Authenticate with Voicemail passwords.
263                 </synopsis>
264                 <syntax>
265                         <parameter name="mailbox" required="true" argsep="@">
266                                 <argument name="mailbox" />
267                                 <argument name="context" />
268                         </parameter>
269                         <parameter name="options">
270                                 <optionlist>
271                                         <option name="s">
272                                                 <para>Skip playing the initial prompts.</para>
273                                         </option>
274                                 </optionlist>
275                         </parameter>
276                 </syntax>
277                 <description>
278                         <para>This application behaves the same way as the Authenticate application, but the passwords
279                         are taken from <filename>voicemail.conf</filename>. If the <replaceable>mailbox</replaceable> is
280                         specified, only that mailbox's password will be considered valid. If the <replaceable>mailbox</replaceable>
281                         is not specified, the channel variable <variable>AUTH_MAILBOX</variable> will be set with the authenticated
282                         mailbox.</para>
283                 </description>
284         </application>
285         <function name="MAILBOX_EXISTS" language="en_US">
286                 <synopsis>
287                         Tell if a mailbox is configured.
288                 </synopsis>
289                 <syntax argsep="@">
290                         <parameter name="mailbox" required="true" />
291                         <parameter name="context" />
292                 </syntax>
293                 <description>
294                         <para>Returns a boolean of whether the corresponding <replaceable>mailbox</replaceable> exists.
295                         If <replaceable>context</replaceable> is not specified, defaults to the <literal>default</literal>
296                         context.</para>
297                 </description>
298         </function>
299  ***/
300
301 #ifdef IMAP_STORAGE
302 static char imapserver[48];
303 static char imapport[8];
304 static char imapflags[128];
305 static char imapfolder[64];
306 static char imapparentfolder[64] = "\0";
307 static char greetingfolder[64];
308 static char authuser[32];
309 static char authpassword[42];
310
311 static int expungeonhangup = 1;
312 static int imapgreetings = 0;
313 static char delimiter = '\0';
314
315 struct vm_state;
316 struct ast_vm_user;
317
318 /* Forward declarations for IMAP */
319 static int init_mailstream(struct vm_state *vms, int box);
320 static void write_file(char *filename, char *buffer, unsigned long len);
321 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len);
322 static void vm_imap_delete(int msgnum, struct ast_vm_user *vmu);
323 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len);
324 static struct vm_state *get_vm_state_by_imapuser(char *user, int interactive);
325 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive);
326 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu);
327 static void vmstate_insert(struct vm_state *vms);
328 static void vmstate_delete(struct vm_state *vms);
329 static void set_update(MAILSTREAM * stream);
330 static void init_vm_state(struct vm_state *vms);
331 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro);
332 static void get_mailbox_delimiter(MAILSTREAM *stream);
333 static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota);
334 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int target);
335 static int imap_store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag);
336 static void update_messages_by_imapuser(const char *user, unsigned long number);
337 static int vm_delete(char *file);
338
339 static int imap_remove_file (char *dir, int msgnum);
340 static int imap_retrieve_file (const char *dir, const int msgnum, const char *mailbox, const char *context);
341 static int imap_delete_old_greeting (char *dir, struct vm_state *vms);
342 static void check_quota(struct vm_state *vms, char *mailbox);
343 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
344 struct vmstate {
345         struct vm_state *vms;
346         AST_LIST_ENTRY(vmstate) list;
347 };
348
349 static AST_LIST_HEAD_STATIC(vmstates, vmstate);
350
351 #endif
352
353 #define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
354
355 #define COMMAND_TIMEOUT 5000
356 /* Don't modify these here; set your umask at runtime instead */
357 #define VOICEMAIL_DIR_MODE      0777
358 #define VOICEMAIL_FILE_MODE     0666
359 #define CHUNKSIZE       65536
360
361 #define VOICEMAIL_CONFIG "voicemail.conf"
362 #define ASTERISK_USERNAME "asterisk"
363
364 /* Define fast-forward, pause, restart, and reverse keys
365    while listening to a voicemail message - these are
366    strings, not characters */
367 #define DEFAULT_LISTEN_CONTROL_FORWARD_KEY "#"
368 #define DEFAULT_LISTEN_CONTROL_REVERSE_KEY "*"
369 #define DEFAULT_LISTEN_CONTROL_PAUSE_KEY "0"
370 #define DEFAULT_LISTEN_CONTROL_RESTART_KEY "2"
371 #define DEFAULT_LISTEN_CONTROL_STOP_KEY "13456789"
372 #define VALID_DTMF "1234567890*#" /* Yes ABCD are valid dtmf but what phones have those? */
373
374 /* Default mail command to mail voicemail. Change it with the
375     mailcmd= command in voicemail.conf */
376 #define SENDMAIL "/usr/sbin/sendmail -t"
377
378 #define INTRO "vm-intro"
379
380 #define MAXMSG 100
381 #define MAXMSGLIMIT 9999
382
383 #define MINPASSWORD 0 /*!< Default minimum mailbox password length */
384
385 #define BASELINELEN 72
386 #define BASEMAXINLINE 256
387 #define eol "\r\n"
388
389 #define MAX_DATETIME_FORMAT     512
390 #define MAX_NUM_CID_CONTEXTS 10
391
392 #define VM_REVIEW        (1 << 0)   /*!< After recording, permit the caller to review the recording before saving */
393 #define VM_OPERATOR      (1 << 1)   /*!< Allow 0 to be pressed to go to 'o' extension */
394 #define VM_SAYCID        (1 << 2)   /*!< Repeat the CallerID info during envelope playback */
395 #define VM_SVMAIL        (1 << 3)   /*!< Allow the user to compose a new VM from within VoicemailMain */
396 #define VM_ENVELOPE      (1 << 4)   /*!< Play the envelope information (who-from, time received, etc.) */
397 #define VM_SAYDURATION   (1 << 5)   /*!< Play the length of the message during envelope playback */
398 #define VM_SKIPAFTERCMD  (1 << 6)   /*!< After deletion, assume caller wants to go to the next message */
399 #define VM_FORCENAME     (1 << 7)   /*!< Have new users record their name */
400 #define VM_FORCEGREET    (1 << 8)   /*!< Have new users record their greetings */
401 #define VM_PBXSKIP       (1 << 9)   /*!< Skip the [PBX] preamble in the Subject line of emails */
402 #define VM_DIRECFORWARD  (1 << 10)  /*!< Permit caller to use the Directory app for selecting to which mailbox to forward a VM */
403 #define VM_ATTACH        (1 << 11)  /*!< Attach message to voicemail notifications? */
404 #define VM_DELETE        (1 << 12)  /*!< Delete message after sending notification */
405 #define VM_ALLOCED       (1 << 13)  /*!< Structure was malloc'ed, instead of placed in a return (usually static) buffer */
406 #define VM_SEARCH        (1 << 14)  /*!< Search all contexts for a matching mailbox */
407 #define VM_TEMPGREETWARN (1 << 15)  /*!< Remind user tempgreeting is set */
408 #define VM_MOVEHEARD     (1 << 16)  /*!< Move a "heard" message to Old after listening to it */
409 #define VM_MESSAGEWRAP   (1 << 17)  /*!< Wrap around from the last message to the first, and vice-versa */
410 #define VM_FWDURGAUTO    (1 << 18)  /*!< Autoset of Urgent flag on forwarded Urgent messages set globally */
411 #define ERROR_LOCK_PATH  -100
412
413
414 enum {
415         NEW_FOLDER,
416         OLD_FOLDER,
417         WORK_FOLDER,
418         FAMILY_FOLDER,
419         FRIENDS_FOLDER,
420         GREETINGS_FOLDER
421 } vm_box;
422
423 enum {
424         OPT_SILENT =           (1 << 0),
425         OPT_BUSY_GREETING =    (1 << 1),
426         OPT_UNAVAIL_GREETING = (1 << 2),
427         OPT_RECORDGAIN =       (1 << 3),
428         OPT_PREPEND_MAILBOX =  (1 << 4),
429         OPT_AUTOPLAY =         (1 << 6),
430         OPT_DTMFEXIT =         (1 << 7),
431         OPT_MESSAGE_Urgent =   (1 << 8),
432         OPT_MESSAGE_PRIORITY = (1 << 9)
433 } vm_option_flags;
434
435 enum {
436         OPT_ARG_RECORDGAIN = 0,
437         OPT_ARG_PLAYFOLDER = 1,
438         OPT_ARG_DTMFEXIT   = 2,
439         /* This *must* be the last value in this enum! */
440         OPT_ARG_ARRAY_SIZE = 3,
441 } vm_option_args;
442
443 AST_APP_OPTIONS(vm_app_options, {
444         AST_APP_OPTION('s', OPT_SILENT),
445         AST_APP_OPTION('b', OPT_BUSY_GREETING),
446         AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
447         AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
448         AST_APP_OPTION_ARG('d', OPT_DTMFEXIT, OPT_ARG_DTMFEXIT),
449         AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
450         AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
451         AST_APP_OPTION('U', OPT_MESSAGE_Urgent),
452         AST_APP_OPTION('P', OPT_MESSAGE_PRIORITY)
453 });
454
455 static int load_config(int reload);
456
457 /*! \page vmlang Voicemail Language Syntaxes Supported
458
459         \par Syntaxes supported, not really language codes.
460         \arg \b en    - English
461         \arg \b de    - German
462         \arg \b es    - Spanish
463         \arg \b fr    - French
464         \arg \b it    - Italian
465         \arg \b nl    - Dutch
466         \arg \b pt    - Portuguese
467         \arg \b pt_BR - Portuguese (Brazil)
468         \arg \b gr    - Greek
469         \arg \b no    - Norwegian
470         \arg \b se    - Swedish
471         \arg \b tw    - Chinese (Taiwan)
472         \arg \b ua - Ukrainian
473
474 German requires the following additional soundfile:
475 \arg \b 1F      einE (feminine)
476
477 Spanish requires the following additional soundfile:
478 \arg \b 1M      un (masculine)
479
480 Dutch, Portuguese & Spanish require the following additional soundfiles:
481 \arg \b vm-INBOXs       singular of 'new'
482 \arg \b vm-Olds         singular of 'old/heard/read'
483
484 NB these are plural:
485 \arg \b vm-INBOX        nieuwe (nl)
486 \arg \b vm-Old          oude (nl)
487
488 Polish uses:
489 \arg \b vm-new-a        'new', feminine singular accusative
490 \arg \b vm-new-e        'new', feminine plural accusative
491 \arg \b vm-new-ych      'new', feminine plural genitive
492 \arg \b vm-old-a        'old', feminine singular accusative
493 \arg \b vm-old-e        'old', feminine plural accusative
494 \arg \b vm-old-ych      'old', feminine plural genitive
495 \arg \b digits/1-a      'one', not always same as 'digits/1'
496 \arg \b digits/2-ie     'two', not always same as 'digits/2'
497
498 Swedish uses:
499 \arg \b vm-nytt         singular of 'new'
500 \arg \b vm-nya          plural of 'new'
501 \arg \b vm-gammalt      singular of 'old'
502 \arg \b vm-gamla        plural of 'old'
503 \arg \b digits/ett      'one', not always same as 'digits/1'
504
505 Norwegian uses:
506 \arg \b vm-ny           singular of 'new'
507 \arg \b vm-nye          plural of 'new'
508 \arg \b vm-gammel       singular of 'old'
509 \arg \b vm-gamle        plural of 'old'
510
511 Dutch also uses:
512 \arg \b nl-om           'at'?
513
514 Spanish also uses:
515 \arg \b vm-youhaveno
516
517 Ukrainian requires the following additional soundfile:
518 \arg \b vm-nove         'nove'
519 \arg \b vm-stare        'stare'
520 \arg \b digits/ua/1e    'odne'
521
522 Italian requires the following additional soundfile:
523
524 For vm_intro_it:
525 \arg \b vm-nuovo        new
526 \arg \b vm-nuovi        new plural
527 \arg \b vm-vecchio      old
528 \arg \b vm-vecchi       old plural
529
530 Chinese (Taiwan) requires the following additional soundfile:
531 \arg \b vm-tong         A class-word for call (tong1)
532 \arg \b vm-ri           A class-word for day (ri4)
533 \arg \b vm-you          You (ni3)
534 \arg \b vm-haveno   Have no (mei2 you3)
535 \arg \b vm-have     Have (you3)
536 \arg \b vm-listen   To listen (yao4 ting1)
537
538
539 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
540 spelled among others when you have to change folder. For the above reasons, vm-INBOX
541 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
542
543 */
544
545 struct baseio {
546         int iocp;
547         int iolen;
548         int linelength;
549         int ateof;
550         unsigned char iobuf[BASEMAXINLINE];
551 };
552
553 /*! Structure for linked list of users 
554  * Use ast_vm_user_destroy() to free one of these structures. */
555 struct ast_vm_user {
556         char context[AST_MAX_CONTEXT];   /*!< Voicemail context */
557         char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
558         char password[80];               /*!< Secret pin code, numbers only */
559         char fullname[80];               /*!< Full name, for directory app */
560         char email[80];                  /*!< E-mail address */
561         char pager[80];                  /*!< E-mail address to pager (no attachment) */
562         char serveremail[80];            /*!< From: Mail address */
563         char mailcmd[160];               /*!< Configurable mail command */
564         char language[MAX_LANGUAGE];     /*!< Config: Language setting */
565         char zonetag[80];                /*!< Time zone */
566         char callback[80];
567         char dialout[80];
568         char uniqueid[80];               /*!< Unique integer identifier */
569         char exit[80];
570         char attachfmt[20];              /*!< Attachment format */
571         unsigned int flags;              /*!< VM_ flags */      
572         int saydurationm;
573         int maxmsg;                      /*!< Maximum number of msgs per folder for this mailbox */
574         int maxdeletedmsg;               /*!< Maximum number of deleted msgs saved for this mailbox */
575         int maxsecs;                     /*!< Maximum number of seconds per message for this mailbox */
576 #ifdef IMAP_STORAGE
577         char imapuser[80];               /*!< IMAP server login */
578         char imappassword[80];           /*!< IMAP server password if authpassword not defined */
579 #endif
580         double volgain;                  /*!< Volume gain for voicemails sent via email */
581         AST_LIST_ENTRY(ast_vm_user) list;
582 };
583
584 /*! Voicemail time zones */
585 struct vm_zone {
586         AST_LIST_ENTRY(vm_zone) list;
587         char name[80];
588         char timezone[80];
589         char msg_format[512];
590 };
591
592 #define VMSTATE_MAX_MSG_ARRAY 256
593
594 /*! Voicemail mailbox state */
595 struct vm_state {
596         char curbox[80];
597         char username[80];
598         char context[80];
599         char curdir[PATH_MAX];
600         char vmbox[PATH_MAX];
601         char fn[PATH_MAX];
602         char intro[PATH_MAX];
603         int *deleted;
604         int *heard;
605         int curmsg;
606         int lastmsg;
607         int newmessages;
608         int oldmessages;
609         int urgentmessages;
610         int starting;
611         int repeats;
612 #ifdef IMAP_STORAGE
613         ast_mutex_t lock;
614         int updated;                         /*!< decremented on each mail check until 1 -allows delay */
615         long msgArray[VMSTATE_MAX_MSG_ARRAY];
616         MAILSTREAM *mailstream;
617         int vmArrayIndex;
618         char imapuser[80];                   /*!< IMAP server login */
619         int interactive;
620         char introfn[PATH_MAX];              /*!< Name of prepended file */
621         unsigned int quota_limit;
622         unsigned int quota_usage;
623         struct vm_state *persist_vms;
624 #endif
625 };
626
627 #ifdef ODBC_STORAGE
628 static char odbc_database[80];
629 static char odbc_table[80];
630 #define RETRIEVE(a,b,c,d) retrieve_file(a,b)
631 #define DISPOSE(a,b) remove_file(a,b)
632 #define STORE(a,b,c,d,e,f,g,h,i,j) store_file(a,b,c,d)
633 #define EXISTS(a,b,c,d) (message_exists(a,b))
634 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
635 #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
636 #define DELETE(a,b,c,d) (delete_file(a,b))
637 #else
638 #ifdef IMAP_STORAGE
639 #define DISPOSE(a,b) (imap_remove_file(a,b))
640 #define STORE(a,b,c,d,e,f,g,h,i,j) (imap_store_file(a,b,c,d,e,f,g,h,i,j))
641 #define RETRIEVE(a,b,c,d) imap_retrieve_file(a,b,c,d)
642 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
643 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
644 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
645 #define DELETE(a,b,c,d) (vm_imap_delete(b,d))
646 #else
647 #define RETRIEVE(a,b,c,d)
648 #define DISPOSE(a,b)
649 #define STORE(a,b,c,d,e,f,g,h,i,j)
650 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
651 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
652 #define COPY(a,b,c,d,e,f,g,h) (copy_plain_file(g,h)); 
653 #define DELETE(a,b,c,d) (vm_delete(c))
654 #endif
655 #endif
656
657 static char VM_SPOOL_DIR[PATH_MAX];
658
659 static char ext_pass_cmd[128];
660 static char ext_pass_check_cmd[128];
661
662 int my_umask;
663
664 #define PWDCHANGE_INTERNAL (1 << 1)
665 #define PWDCHANGE_EXTERNAL (1 << 2)
666 static int pwdchange = PWDCHANGE_INTERNAL;
667
668 #ifdef ODBC_STORAGE
669 #define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
670 #else
671 # ifdef IMAP_STORAGE
672 # define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
673 # else
674 # define tdesc "Comedian Mail (Voicemail System)"
675 # endif
676 #endif
677
678 static char userscontext[AST_MAX_EXTENSION] = "default";
679
680 static char *addesc = "Comedian Mail";
681
682 /* Leave a message */
683 static char *app = "VoiceMail";
684
685 /* Check mail, control, etc */
686 static char *app2 = "VoiceMailMain";
687
688 static char *app3 = "MailboxExists";
689 static char *app4 = "VMAuthenticate";
690
691 static AST_LIST_HEAD_STATIC(users, ast_vm_user);
692 static AST_LIST_HEAD_STATIC(zones, vm_zone);
693 static char zonetag[80];
694 static int maxsilence;
695 static int maxmsg;
696 static int maxdeletedmsg;
697 static int silencethreshold = 128;
698 static char serveremail[80];
699 static char mailcmd[160];       /* Configurable mail cmd */
700 static char externnotify[160]; 
701 static struct ast_smdi_interface *smdi_iface = NULL;
702 static char vmfmts[80];
703 static double volgain;
704 static int vmminsecs;
705 static int vmmaxsecs;
706 static int maxgreet;
707 static int skipms;
708 static int maxlogins;
709 static int minpassword;
710
711 /*! Poll mailboxes for changes since there is something external to
712  *  app_voicemail that may change them. */
713 static unsigned int poll_mailboxes;
714
715 /*! Polling frequency */
716 static unsigned int poll_freq;
717 /*! By default, poll every 30 seconds */
718 #define DEFAULT_POLL_FREQ 30
719
720 AST_MUTEX_DEFINE_STATIC(poll_lock);
721 static ast_cond_t poll_cond = PTHREAD_COND_INITIALIZER;
722 static pthread_t poll_thread = AST_PTHREADT_NULL;
723 static unsigned char poll_thread_run;
724
725 /*! Subscription to ... MWI event subscriptions */
726 static struct ast_event_sub *mwi_sub_sub;
727 /*! Subscription to ... MWI event un-subscriptions */
728 static struct ast_event_sub *mwi_unsub_sub;
729
730 /*!
731  * \brief An MWI subscription
732  *
733  * This is so we can keep track of which mailboxes are subscribed to.
734  * This way, we know which mailboxes to poll when the pollmailboxes
735  * option is being used.
736  */
737 struct mwi_sub {
738         AST_RWLIST_ENTRY(mwi_sub) entry;
739         int old_urgent;
740         int old_new;
741         int old_old;
742         uint32_t uniqueid;
743         char mailbox[1];
744 };
745
746 struct mwi_sub_task {
747         const char *mailbox;
748         const char *context;
749         uint32_t uniqueid;
750 };
751
752 static struct ast_taskprocessor *mwi_subscription_tps;
753
754 static AST_RWLIST_HEAD_STATIC(mwi_subs, mwi_sub);
755
756 /* custom audio control prompts for voicemail playback */
757 static char listen_control_forward_key[12];
758 static char listen_control_reverse_key[12];
759 static char listen_control_pause_key[12];
760 static char listen_control_restart_key[12];
761 static char listen_control_stop_key[12];
762
763 /* custom password sounds */
764 static char vm_password[80] = "vm-password";
765 static char vm_newpassword[80] = "vm-newpassword";
766 static char vm_passchanged[80] = "vm-passchanged";
767 static char vm_reenterpassword[80] = "vm-reenterpassword";
768 static char vm_mismatch[80] = "vm-mismatch";
769 static char vm_invalid_password[80] = "vm-invalid-password";
770
771 static struct ast_flags globalflags = {0};
772
773 static int saydurationminfo;
774
775 static char dialcontext[AST_MAX_CONTEXT] = "";
776 static char callcontext[AST_MAX_CONTEXT] = "";
777 static char exitcontext[AST_MAX_CONTEXT] = "";
778
779 static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
780
781
782 static char *emailbody = NULL;
783 static char *emailsubject = NULL;
784 static char *pagerbody = NULL;
785 static char *pagersubject = NULL;
786 static char fromstring[100];
787 static char pagerfromstring[100];
788 static char charset[32] = "ISO-8859-1";
789
790 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
791 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
792 static int adsiver = 1;
793 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
794
795 /* Forward declarations - generic */
796 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
797 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);
798 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
799 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
800                         char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
801                         signed char record_gain, struct vm_state *vms, char *flag);
802 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
803 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
804 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);
805 static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, 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);
806 static void apply_options(struct ast_vm_user *vmu, const char *options);
807 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);
808 static int is_valid_dtmf(const char *key);
809
810 #if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
811 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
812 #endif
813
814 static char *strip_control(const char *input, char *buf, size_t buflen)
815 {
816         char *bufptr = buf;
817         for (; *input; input++) {
818                 if (*input < 32) {
819                         continue;
820                 }
821                 *bufptr++ = *input;
822                 if (bufptr == buf + buflen - 1) {
823                         break;
824                 }
825         }
826         *bufptr = '\0';
827         return buf;
828 }
829
830
831 /*!
832  * \brief Sets default voicemail system options to a voicemail user.
833  *
834  * This applies select global settings to a newly created (dynamic) instance of a voicemail user.
835  * - all the globalflags
836  * - the saydurationminfo
837  * - the callcontext
838  * - the dialcontext
839  * - the exitcontext
840  * - vmmaxsecs, vmmaxmsg, maxdeletedmsg
841  * - volume gain.
842  */
843 static void populate_defaults(struct ast_vm_user *vmu)
844 {
845         ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);     
846         if (saydurationminfo)
847                 vmu->saydurationm = saydurationminfo;
848         ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
849         ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
850         ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
851         ast_copy_string(vmu->zonetag, zonetag, sizeof(vmu->zonetag));
852         if (vmmaxsecs)
853                 vmu->maxsecs = vmmaxsecs;
854         if (maxmsg)
855                 vmu->maxmsg = maxmsg;
856         if (maxdeletedmsg)
857                 vmu->maxdeletedmsg = maxdeletedmsg;
858         vmu->volgain = volgain;
859 }
860
861 /*!
862  * \brief Sets a a specific property value.
863  * \param vmu The voicemail user object to work with.
864  * \param var The name of the property to be set.
865  * \param value The value to be set to the property.
866  * 
867  * The property name must be one of the understood properties. See the source for details.
868  */
869 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
870 {
871         int x;
872         if (!strcasecmp(var, "attach")) {
873                 ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
874         } else if (!strcasecmp(var, "attachfmt")) {
875                 ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
876         } else if (!strcasecmp(var, "serveremail")) {
877                 ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
878         } else if (!strcasecmp(var, "language")) {
879                 ast_copy_string(vmu->language, value, sizeof(vmu->language));
880         } else if (!strcasecmp(var, "tz")) {
881                 ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
882 #ifdef IMAP_STORAGE
883         } else if (!strcasecmp(var, "imapuser")) {
884                 ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
885         } else if (!strcasecmp(var, "imappassword") || !strcasecmp(var, "imapsecret")) {
886                 ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
887 #endif
888         } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
889                 ast_set2_flag(vmu, ast_true(value), VM_DELETE); 
890         } else if (!strcasecmp(var, "saycid")){
891                 ast_set2_flag(vmu, ast_true(value), VM_SAYCID); 
892         } else if (!strcasecmp(var,"sendvoicemail")){
893                 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL); 
894         } else if (!strcasecmp(var, "review")){
895                 ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
896         } else if (!strcasecmp(var, "tempgreetwarn")){
897                 ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);  
898         } else if (!strcasecmp(var, "messagewrap")){
899                 ast_set2_flag(vmu, ast_true(value), VM_MESSAGEWRAP);    
900         } else if (!strcasecmp(var, "operator")) {
901                 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);       
902         } else if (!strcasecmp(var, "envelope")){
903                 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);       
904         } else if (!strcasecmp(var, "moveheard")){
905                 ast_set2_flag(vmu, ast_true(value), VM_MOVEHEARD);
906         } else if (!strcasecmp(var, "sayduration")){
907                 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);    
908         } else if (!strcasecmp(var, "saydurationm")){
909                 if (sscanf(value, "%d", &x) == 1) {
910                         vmu->saydurationm = x;
911                 } else {
912                         ast_log(AST_LOG_WARNING, "Invalid min duration for say duration\n");
913                 }
914         } else if (!strcasecmp(var, "forcename")){
915                 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);      
916         } else if (!strcasecmp(var, "forcegreetings")){
917                 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);     
918         } else if (!strcasecmp(var, "callback")) {
919                 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
920         } else if (!strcasecmp(var, "dialout")) {
921                 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
922         } else if (!strcasecmp(var, "exitcontext")) {
923                 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
924         } else if (!strcasecmp(var, "maxmessage") || !strcasecmp(var, "maxsecs")) {
925                 if (vmu->maxsecs <= 0) {
926                         ast_log(AST_LOG_WARNING, "Invalid max message length of %s. Using global value %d\n", value, vmmaxsecs);
927                         vmu->maxsecs = vmmaxsecs;
928                 } else {
929                         vmu->maxsecs = atoi(value);
930                 }
931                 if (!strcasecmp(var, "maxmessage"))
932                         ast_log(AST_LOG_WARNING, "Option 'maxmessage' has been deprecated in favor of 'maxsecs'.  Please make that change in your voicemail config.\n");
933         } else if (!strcasecmp(var, "maxmsg")) {
934                 vmu->maxmsg = atoi(value);
935                 if (vmu->maxmsg <= 0) {
936                         ast_log(AST_LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %d\n", value, MAXMSG);
937                         vmu->maxmsg = MAXMSG;
938                 } else if (vmu->maxmsg > MAXMSGLIMIT) {
939                         ast_log(AST_LOG_WARNING, "Maximum number of messages per folder is %d. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
940                         vmu->maxmsg = MAXMSGLIMIT;
941                 }
942         } else if (!strcasecmp(var, "backupdeleted")) {
943                 if (sscanf(value, "%d", &x) == 1)
944                         vmu->maxdeletedmsg = x;
945                 else if (ast_true(value))
946                         vmu->maxdeletedmsg = MAXMSG;
947                 else
948                         vmu->maxdeletedmsg = 0;
949
950                 if (vmu->maxdeletedmsg < 0) {
951                         ast_log(AST_LOG_WARNING, "Invalid number of deleted messages saved per mailbox backupdeleted=%s. Using default value %d\n", value, MAXMSG);
952                         vmu->maxdeletedmsg = MAXMSG;
953                 } else if (vmu->maxdeletedmsg > MAXMSGLIMIT) {
954                         ast_log(AST_LOG_WARNING, "Maximum number of deleted messages saved per mailbox is %d. Cannot accept value backupdeleted=%s\n", MAXMSGLIMIT, value);
955                         vmu->maxdeletedmsg = MAXMSGLIMIT;
956                 }
957         } else if (!strcasecmp(var, "volgain")) {
958                 sscanf(value, "%lf", &vmu->volgain);
959         } else if (!strcasecmp(var, "options")) {
960                 apply_options(vmu, value);
961         }
962 }
963
964 static char *vm_check_password_shell(char *command, char *buf, size_t len) 
965 {
966         int fds[2], pid = 0;
967
968         memset(buf, 0, len);
969
970         if (pipe(fds)) {
971                 snprintf(buf, len, "FAILURE: Pipe failed: %s", strerror(errno));
972         } else {
973                 /* good to go*/
974                 pid = ast_safe_fork(0);
975
976                 if (pid < 0) {
977                         /* ok maybe not */
978                         close(fds[0]);
979                         close(fds[1]);
980                         snprintf(buf, len, "FAILURE: Fork failed");
981                 } else if (pid) {
982                         /* parent */
983                         close(fds[1]);
984                         if (read(fds[0], buf, len) < 0) {
985                                 ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
986                         }
987                         close(fds[0]);
988                 } else {
989                         /*  child */
990                         AST_DECLARE_APP_ARGS(arg,
991                                 AST_APP_ARG(v)[20];
992                         );
993                         char *mycmd = ast_strdupa(command);
994
995                         close(fds[0]);
996                         dup2(fds[1], STDOUT_FILENO);
997                         close(fds[1]);
998                         ast_close_fds_above_n(STDOUT_FILENO);
999
1000                         AST_NONSTANDARD_APP_ARGS(arg, mycmd, ' ');
1001
1002                         execv(arg.v[0], arg.v); 
1003                         printf("FAILURE: %s", strerror(errno));
1004                         _exit(0);
1005                 }
1006         }
1007         return buf;
1008 }
1009
1010 /*!
1011  * \brief Check that password meets minimum required length
1012  * \param vmu The voicemail user to change the password for.
1013  * \param password The password string to check
1014  *
1015  * \return zero on ok, 1 on not ok.
1016  */
1017 static int check_password(struct ast_vm_user *vmu, char *password)
1018 {
1019         /* check minimum length */
1020         if (strlen(password) < minpassword)
1021                 return 1;
1022         if (!ast_strlen_zero(ext_pass_check_cmd)) {
1023                 char cmd[255], buf[255];
1024
1025                 ast_log(AST_LOG_DEBUG, "Verify password policies for %s\n", password);
1026
1027                 snprintf(cmd, sizeof(cmd), "%s %s %s %s %s", ext_pass_check_cmd, vmu->mailbox, vmu->context, vmu->password, password);
1028                 if (vm_check_password_shell(cmd, buf, sizeof(buf))) {
1029                         ast_debug(5, "Result: %s\n", buf);
1030                         if (!strncasecmp(buf, "VALID", 5)) {
1031                                 ast_debug(3, "Passed password check: '%s'\n", buf);
1032                                 return 0;
1033                         } else if (!strncasecmp(buf, "FAILURE", 7)) {
1034                                 ast_log(AST_LOG_WARNING, "Unable to execute password validation script: '%s'.\n", buf);
1035                                 return 0;
1036                         } else {
1037                                 ast_log(AST_LOG_NOTICE, "Password doesn't match policies for user %s %s\n", vmu->mailbox, password);
1038                                 return 1;
1039                         }
1040                 }
1041         }
1042         return 0;
1043 }
1044
1045 /*! 
1046  * \brief Performs a change of the voicemail passowrd in the realtime engine.
1047  * \param vmu The voicemail user to change the password for.
1048  * \param password The new value to be set to the password for this user.
1049  * 
1050  * This only works if the voicemail user has a unique id, and if there is a realtime engine configured.
1051  * This is called from the (top level) vm_change_password.
1052  *
1053  * \return zero on success, -1 on error.
1054  */
1055 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
1056 {
1057         int res;
1058         if (!ast_strlen_zero(vmu->uniqueid)) {
1059                 if (strlen(password) > 10) {
1060                         ast_realtime_require_field("voicemail", "password", RQ_CHAR, strlen(password), SENTINEL);
1061                 }
1062                 res = ast_update2_realtime("voicemail", "context", vmu->context, "mailbox", vmu->mailbox, SENTINEL, "password", password, SENTINEL);
1063                 if (res > 0) {
1064                         ast_copy_string(vmu->password, password, sizeof(vmu->password));
1065                         res = 0;
1066                 } else if (!res) {
1067                         res = -1;
1068                 }
1069                 return res;
1070         }
1071         return -1;
1072 }
1073
1074 /*!
1075  * \brief Destructively Parse options and apply.
1076  */
1077 static void apply_options(struct ast_vm_user *vmu, const char *options)
1078 {       
1079         char *stringp;
1080         char *s;
1081         char *var, *value;
1082         stringp = ast_strdupa(options);
1083         while ((s = strsep(&stringp, "|"))) {
1084                 value = s;
1085                 if ((var = strsep(&value, "=")) && value) {
1086                         apply_option(vmu, var, value);
1087                 }
1088         }       
1089 }
1090
1091 /*!
1092  * \brief Loads the options specific to a voicemail user.
1093  * 
1094  * This is called when a vm_user structure is being set up, such as from load_options.
1095  */
1096 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
1097 {
1098         for (; var; var = var->next) {
1099                 if (!strcasecmp(var->name, "vmsecret")) {
1100                         ast_copy_string(retval->password, var->value, sizeof(retval->password));
1101                 } else if (!strcasecmp(var->name, "secret") || !strcasecmp(var->name, "password")) { /* don't overwrite vmsecret if it exists */
1102                         if (ast_strlen_zero(retval->password))
1103                                 ast_copy_string(retval->password, var->value, sizeof(retval->password));
1104                 } else if (!strcasecmp(var->name, "uniqueid")) {
1105                         ast_copy_string(retval->uniqueid, var->value, sizeof(retval->uniqueid));
1106                 } else if (!strcasecmp(var->name, "pager")) {
1107                         ast_copy_string(retval->pager, var->value, sizeof(retval->pager));
1108                 } else if (!strcasecmp(var->name, "email")) {
1109                         ast_copy_string(retval->email, var->value, sizeof(retval->email));
1110                 } else if (!strcasecmp(var->name, "fullname")) {
1111                         ast_copy_string(retval->fullname, var->value, sizeof(retval->fullname));
1112                 } else if (!strcasecmp(var->name, "context")) {
1113                         ast_copy_string(retval->context, var->value, sizeof(retval->context));
1114 #ifdef IMAP_STORAGE
1115                 } else if (!strcasecmp(var->name, "imapuser")) {
1116                         ast_copy_string(retval->imapuser, var->value, sizeof(retval->imapuser));
1117                 } else if (!strcasecmp(var->name, "imappassword") || !strcasecmp(var->name, "imapsecret")) {
1118                         ast_copy_string(retval->imappassword, var->value, sizeof(retval->imappassword));
1119 #endif
1120                 } else
1121                         apply_option(retval, var->name, var->value);
1122         }
1123 }
1124
1125 /*!
1126  * \brief Determines if a DTMF key entered is valid.
1127  * \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.
1128  *
1129  * Tests the character entered against the set of valid DTMF characters. 
1130  * \return 1 if the character entered is a valid DTMF digit, 0 if the character is invalid.
1131  */
1132 static int is_valid_dtmf(const char *key)
1133 {
1134         int i;
1135         char *local_key = ast_strdupa(key);
1136
1137         for (i = 0; i < strlen(key); ++i) {
1138                 if (!strchr(VALID_DTMF, *local_key)) {
1139                         ast_log(AST_LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
1140                         return 0;
1141                 }
1142                 local_key++;
1143         }
1144         return 1;
1145 }
1146
1147 /*!
1148  * \brief Finds a voicemail user from the realtime engine.
1149  * \param ivm
1150  * \param context
1151  * \param mailbox
1152  *
1153  * 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.
1154  *
1155  * \return The ast_vm_user structure for the user that was found.
1156  */
1157 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1158 {
1159         struct ast_variable *var;
1160         struct ast_vm_user *retval;
1161
1162         if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
1163                 if (!ivm)
1164                         ast_set_flag(retval, VM_ALLOCED);       
1165                 else
1166                         memset(retval, 0, sizeof(*retval));
1167                 if (mailbox) 
1168                         ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
1169                 populate_defaults(retval);
1170                 if (!context && ast_test_flag((&globalflags), VM_SEARCH))
1171                         var = ast_load_realtime("voicemail", "mailbox", mailbox, SENTINEL);
1172                 else
1173                         var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, SENTINEL);
1174                 if (var) {
1175                         apply_options_full(retval, var);
1176                         ast_variables_destroy(var);
1177                 } else { 
1178                         if (!ivm) 
1179                                 ast_free(retval);
1180                         retval = NULL;
1181                 }       
1182         } 
1183         return retval;
1184 }
1185
1186 /*!
1187  * \brief Finds a voicemail user from the users file or the realtime engine.
1188  * \param ivm
1189  * \param context
1190  * \param mailbox
1191  * 
1192  * \return The ast_vm_user structure for the user that was found.
1193  */
1194 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1195 {
1196         /* This function could be made to generate one from a database, too */
1197         struct ast_vm_user *vmu=NULL, *cur;
1198         AST_LIST_LOCK(&users);
1199
1200         if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
1201                 context = "default";
1202
1203         AST_LIST_TRAVERSE(&users, cur, list) {
1204                 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
1205                         break;
1206                 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
1207                         break;
1208         }
1209         if (cur) {
1210                 /* Make a copy, so that on a reload, we have no race */
1211                 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
1212                         memcpy(vmu, cur, sizeof(*vmu));
1213                         ast_set2_flag(vmu, !ivm, VM_ALLOCED);
1214                         AST_LIST_NEXT(vmu, list) = NULL;
1215                 }
1216         } else
1217                 vmu = find_user_realtime(ivm, context, mailbox);
1218         AST_LIST_UNLOCK(&users);
1219         return vmu;
1220 }
1221
1222 /*!
1223  * \brief Resets a user password to a specified password.
1224  * \param context
1225  * \param mailbox
1226  * \param newpass
1227  *
1228  * This does the actual change password work, called by the vm_change_password() function.
1229  *
1230  * \return zero on success, -1 on error.
1231  */
1232 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
1233 {
1234         /* This function could be made to generate one from a database, too */
1235         struct ast_vm_user *cur;
1236         int res = -1;
1237         AST_LIST_LOCK(&users);
1238         AST_LIST_TRAVERSE(&users, cur, list) {
1239                 if ((!context || !strcasecmp(context, cur->context)) &&
1240                         (!strcasecmp(mailbox, cur->mailbox)))
1241                                 break;
1242         }
1243         if (cur) {
1244                 ast_copy_string(cur->password, newpass, sizeof(cur->password));
1245                 res = 0;
1246         }
1247         AST_LIST_UNLOCK(&users);
1248         return res;
1249 }
1250
1251 /*! 
1252  * \brief The handler for the change password option.
1253  * \param vmu The voicemail user to work with.
1254  * \param newpassword The new password (that has been gathered from the appropriate prompting).
1255  * 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.
1256  * It is also called when the user wants to change their password from menu option '5' on the mailbox options menu.
1257  */
1258 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
1259 {
1260         struct ast_config   *cfg=NULL;
1261         struct ast_variable *var=NULL;
1262         struct ast_category *cat=NULL;
1263         char *category=NULL, *value=NULL, *new=NULL;
1264         const char *tmp=NULL;
1265         struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
1266         if (!change_password_realtime(vmu, newpassword))
1267                 return;
1268
1269         /* check voicemail.conf */
1270         if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1271                 while ((category = ast_category_browse(cfg, category))) {
1272                         if (!strcasecmp(category, vmu->context)) {
1273                                 if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
1274                                         ast_log(AST_LOG_WARNING, "We could not find the mailbox.\n");
1275                                         break;
1276                                 }
1277                                 value = strstr(tmp,",");
1278                                 if (!value) {
1279                                         ast_log(AST_LOG_WARNING, "variable has bad format.\n");
1280                                         break;
1281                                 }
1282                                 new = alloca((strlen(value)+strlen(newpassword)+1));
1283                                 sprintf(new,"%s%s", newpassword, value);
1284                                 if (!(cat = ast_category_get(cfg, category))) {
1285                                         ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
1286                                         break;
1287                                 }
1288                                 ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
1289                         }
1290                 }
1291                 /* save the results */
1292                 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1293                 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1294                 ast_config_text_file_save(VOICEMAIL_CONFIG, cfg, "AppVoicemail");
1295         }
1296         category = NULL;
1297         var = NULL;
1298         /* check users.conf and update the password stored for the mailbox*/
1299         /* if no vmsecret entry exists create one. */
1300         if ((cfg = ast_config_load("users.conf", config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1301                 ast_debug(4, "we are looking for %s\n", vmu->mailbox);
1302                 while ((category = ast_category_browse(cfg, category))) {
1303                         ast_debug(4, "users.conf: %s\n", category);
1304                         if (!strcasecmp(category, vmu->mailbox)) {
1305                                 if (!(tmp = ast_variable_retrieve(cfg, category, "vmsecret"))) {
1306                                         ast_debug(3, "looks like we need to make vmsecret!\n");
1307                                         var = ast_variable_new("vmsecret", newpassword, "");
1308                                 } 
1309                                 new = alloca(strlen(newpassword)+1);
1310                                 sprintf(new, "%s", newpassword);
1311                                 if (!(cat = ast_category_get(cfg, category))) {
1312                                         ast_debug(4, "failed to get category!\n");
1313                                         break;
1314                                 }
1315                                 if (!var)               
1316                                         ast_variable_update(cat, "vmsecret", new, NULL, 0);
1317                                 else
1318                                         ast_variable_append(cat, var);
1319                         }
1320                 }
1321                 /* save the results and clean things up */
1322                 reset_user_pw(vmu->context, vmu->mailbox, newpassword); 
1323                 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1324                 ast_config_text_file_save("users.conf", cfg, "AppVoicemail");
1325         }
1326 }
1327
1328 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
1329 {
1330         char buf[255];
1331         snprintf(buf,255,"%s %s %s %s",ext_pass_cmd,vmu->context,vmu->mailbox,newpassword);
1332         if (!ast_safe_system(buf)) {
1333                 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1334                 /* Reset the password in memory, too */
1335                 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1336         }
1337 }
1338
1339 /*! 
1340  * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1341  * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1342  * \param len The length of the path string that was written out.
1343  * 
1344  * The path is constructed as 
1345  *      VM_SPOOL_DIRcontext/ext/folder
1346  *
1347  * \return zero on success, -1 on error.
1348  */
1349 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
1350 {
1351         return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
1352 }
1353
1354 /*! 
1355  * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1356  * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1357  * \param len The length of the path string that was written out.
1358  * 
1359  * The path is constructed as 
1360  *      VM_SPOOL_DIRcontext/ext/folder
1361  *
1362  * \return zero on success, -1 on error.
1363  */
1364 static int make_file(char *dest, const int len, const char *dir, const int num)
1365 {
1366         return snprintf(dest, len, "%s/msg%04d", dir, num);
1367 }
1368
1369 /* same as mkstemp, but return a FILE * */
1370 static FILE *vm_mkftemp(char *template)
1371 {
1372         FILE *p = NULL;
1373         int pfd = mkstemp(template);
1374         chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
1375         if (pfd > -1) {
1376                 p = fdopen(pfd, "w+");
1377                 if (!p) {
1378                         close(pfd);
1379                         pfd = -1;
1380                 }
1381         }
1382         return p;
1383 }
1384
1385 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
1386  * \param dest    String. base directory.
1387  * \param len     Length of dest.
1388  * \param context String. Ignored if is null or empty string.
1389  * \param ext     String. Ignored if is null or empty string.
1390  * \param folder  String. Ignored if is null or empty string. 
1391  * \return -1 on failure, 0 on success.
1392  */
1393 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
1394 {
1395         mode_t  mode = VOICEMAIL_DIR_MODE;
1396         int res;
1397
1398         make_dir(dest, len, context, ext, folder);
1399         if ((res = ast_mkdir(dest, mode))) {
1400                 ast_log(AST_LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1401                 return -1;
1402         }
1403         return 0;
1404 }
1405
1406 static const char *mbox(int id)
1407 {
1408         static const char *msgs[] = {
1409 #ifdef IMAP_STORAGE
1410                 imapfolder,
1411 #else
1412                 "INBOX",
1413 #endif
1414                 "Old",
1415                 "Work",
1416                 "Family",
1417                 "Friends",
1418                 "Cust1",
1419                 "Cust2",
1420                 "Cust3",
1421                 "Cust4",
1422                 "Cust5",
1423                 "Deleted",
1424                 "Urgent"
1425         };
1426         return (id >= 0 && id < ARRAY_LEN(msgs)) ? msgs[id] : "Unknown";
1427 }
1428
1429 static void free_user(struct ast_vm_user *vmu)
1430 {
1431         if (ast_test_flag(vmu, VM_ALLOCED))
1432                 ast_free(vmu);
1433 }
1434
1435 /* All IMAP-specific functions should go in this block. This
1436  * keeps them from being spread out all over the code */
1437 #ifdef IMAP_STORAGE
1438 static void vm_imap_delete(int msgnum, struct ast_vm_user *vmu)
1439 {
1440         char arg[10];
1441         struct vm_state *vms;
1442         unsigned long messageNum;
1443
1444         /* Greetings aren't stored in IMAP, so we can't delete them there */
1445         if (msgnum < 0) {
1446                 return;
1447         }
1448
1449         if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1450                 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);
1451                 return;
1452         }
1453
1454         /* find real message number based on msgnum */
1455         /* this may be an index into vms->msgArray based on the msgnum. */
1456         messageNum = vms->msgArray[msgnum];
1457         if (messageNum == 0) {
1458                 ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n",msgnum,messageNum);
1459                 return;
1460         }
1461         if (option_debug > 2)
1462                 ast_log(LOG_DEBUG, "deleting msgnum %d, which is mailbox message %lu\n",msgnum,messageNum);
1463         /* delete message */
1464         snprintf (arg, sizeof(arg), "%lu",messageNum);
1465         mail_setflag (vms->mailstream,arg,"\\DELETED");
1466 }
1467
1468 static int imap_retrieve_greeting (const char *dir, const int msgnum, struct ast_vm_user *vmu)
1469 {
1470         struct vm_state *vms_p;
1471         char *file, *filename;
1472         char *attachment;
1473         int ret = 0, i;
1474         BODY *body;
1475
1476         /* This function is only used for retrieval of IMAP greetings
1477          * regular messages are not retrieved this way, nor are greetings
1478          * if they are stored locally*/
1479         if (msgnum > -1 || !imapgreetings) {
1480                 return 0;
1481         } else {
1482                 file = strrchr(ast_strdupa(dir), '/');
1483                 if (file)
1484                         *file++ = '\0';
1485                 else {
1486                         ast_debug (1, "Failed to procure file name from directory passed.\n");
1487                         return -1;
1488                 }
1489         }
1490
1491         /* check if someone is accessing this box right now... */
1492         if (!(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) ||!(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1493                 ast_log(AST_LOG_ERROR, "Voicemail state not found!\n");
1494                 return -1;
1495         }
1496         
1497         /* Greetings will never have a prepended message */
1498         *vms_p->introfn = '\0';
1499         
1500         ret = init_mailstream(vms_p, GREETINGS_FOLDER);
1501         if (!vms_p->mailstream) {
1502                 ast_log(AST_LOG_ERROR, "IMAP mailstream is NULL\n");
1503                 return -1;
1504         }
1505
1506         /*XXX Yuck, this could probably be done a lot better */
1507         for (i = 0; i < vms_p->mailstream->nmsgs; i++) {
1508                 mail_fetchstructure(vms_p->mailstream, i + 1, &body);
1509                 /* We have the body, now we extract the file name of the first attachment. */
1510                 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1511                         attachment = ast_strdupa(body->nested.part->next->body.parameter->value);
1512                 } else {
1513                         ast_log(AST_LOG_ERROR, "There is no file attached to this IMAP message.\n");
1514                         return -1;
1515                 }
1516                 filename = strsep(&attachment, ".");
1517                 if (!strcmp(filename, file)) {
1518                         ast_copy_string(vms_p->fn, dir, sizeof(vms_p->fn));
1519                         vms_p->msgArray[vms_p->curmsg] = i + 1;
1520                         save_body(body, vms_p, "2", attachment, 0);
1521                         return 0;
1522                 }
1523         }
1524
1525         return -1;
1526 }
1527
1528 static int imap_retrieve_file(const char *dir, const int msgnum, const char *mailbox, const char *context)
1529 {
1530         BODY *body;
1531         char *header_content;
1532         char *attachedfilefmt;
1533         char buf[80];
1534         struct vm_state *vms;
1535         char text_file[PATH_MAX];
1536         FILE *text_file_ptr;
1537         int res = 0;
1538         struct ast_vm_user *vmu;
1539
1540         if (!(vmu = find_user(NULL, context, mailbox))) {
1541                 ast_log(LOG_WARNING, "Couldn't find user with mailbox %s@%s\n", mailbox, context);
1542                 return -1;
1543         }
1544         
1545         if (msgnum < 0) {
1546                 if (imapgreetings) {
1547                         res = imap_retrieve_greeting(dir, msgnum, vmu);
1548                         goto exit;
1549                 } else {
1550                         res = 0;
1551                         goto exit;
1552                 }
1553         }
1554
1555         /* Before anything can happen, we need a vm_state so that we can
1556          * actually access the imap server through the vms->mailstream
1557          */
1558         if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1559                 /* This should not happen. If it does, then I guess we'd
1560                  * need to create the vm_state, extract which mailbox to
1561                  * open, and then set up the msgArray so that the correct
1562                  * IMAP message could be accessed. If I have seen correctly
1563                  * though, the vms should be obtainable from the vmstates list
1564                  * and should have its msgArray properly set up.
1565                  */
1566                 ast_log(LOG_ERROR, "Couldn't find a vm_state for mailbox %s!!! Oh no!\n", vmu->mailbox);
1567                 res = -1;
1568                 goto exit;
1569         }
1570         
1571         make_file(vms->fn, sizeof(vms->fn), dir, msgnum);
1572         snprintf(vms->introfn, sizeof(vms->introfn), "%sintro", vms->fn);
1573
1574         /* Don't try to retrieve a message from IMAP if it already is on the file system */
1575         if (ast_fileexists(vms->fn, NULL, NULL) > 0) {
1576                 res = 0;
1577                 goto exit;
1578         }
1579
1580         if (option_debug > 2)
1581                 ast_log (LOG_DEBUG,"Before mail_fetchheaders, curmsg is: %d, imap messages is %lu\n", msgnum, vms->msgArray[msgnum]);
1582         if (vms->msgArray[msgnum] == 0) {
1583                 ast_log (LOG_WARNING,"Trying to access unknown message\n");
1584                 res = -1;
1585                 goto exit;
1586         }
1587
1588         /* This will only work for new messages... */
1589         header_content = mail_fetchheader (vms->mailstream, vms->msgArray[msgnum]);
1590         /* empty string means no valid header */
1591         if (ast_strlen_zero(header_content)) {
1592                 ast_log (LOG_ERROR,"Could not fetch header for message number %ld\n",vms->msgArray[msgnum]);
1593                 res = -1;
1594                 goto exit;
1595         }
1596
1597         mail_fetchstructure (vms->mailstream,vms->msgArray[msgnum],&body);
1598         
1599         /* We have the body, now we extract the file name of the first attachment. */
1600         if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1601                 attachedfilefmt = ast_strdupa(body->nested.part->next->body.parameter->value);
1602         } else {
1603                 ast_log(LOG_ERROR, "There is no file attached to this IMAP message.\n");
1604                 res = -1;
1605                 goto exit;
1606         }
1607         
1608         /* Find the format of the attached file */
1609
1610         strsep(&attachedfilefmt, ".");
1611         if (!attachedfilefmt) {
1612                 ast_log(LOG_ERROR, "File format could not be obtained from IMAP message attachment\n");
1613                 res = -1;
1614                 goto exit;
1615         }
1616         
1617         save_body(body, vms, "2", attachedfilefmt, 0);
1618         if (save_body(body, vms, "3", attachedfilefmt, 1)) {
1619                 *vms->introfn = '\0';
1620         }
1621
1622         /* Get info from headers!! */
1623         snprintf(text_file, sizeof(text_file), "%s.%s", vms->fn, "txt");
1624
1625         if (!(text_file_ptr = fopen(text_file, "w"))) {
1626                 ast_log(LOG_WARNING, "Unable to open/create file %s: %s\n", text_file, strerror(errno));
1627         }
1628
1629         fprintf(text_file_ptr, "%s\n", "[message]");
1630
1631         get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf));
1632         fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, ""));
1633         get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf));
1634         fprintf(text_file_ptr, "<%s>\n", S_OR(buf, ""));
1635         get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf));
1636         fprintf(text_file_ptr, "context=%s\n", S_OR(buf, ""));
1637         get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf));
1638         fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, ""));
1639         get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf));
1640         fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, ""));
1641         get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf));
1642         fprintf(text_file_ptr, "category=%s\n", S_OR(buf, ""));
1643         get_header_by_tag(header_content, "X-Asterisk-VM-Flag:", buf, sizeof(buf));
1644         fprintf(text_file_ptr, "flag=%s\n", S_OR(buf, ""));
1645         fclose(text_file_ptr);
1646
1647 exit:
1648         free_user(vmu);
1649         return res;
1650 }
1651
1652 static int folder_int(const char *folder)
1653 {
1654         /*assume a NULL folder means INBOX*/
1655         if (!folder)
1656                 return 0;
1657 #ifdef IMAP_STORAGE
1658         if (!strcasecmp(folder, imapfolder))
1659 #else
1660         if (!strcasecmp(folder, "INBOX"))
1661 #endif
1662                 return 0;
1663         else if (!strcasecmp(folder, "Old"))
1664                 return 1;
1665         else if (!strcasecmp(folder, "Work"))
1666                 return 2;
1667         else if (!strcasecmp(folder, "Family"))
1668                 return 3;
1669         else if (!strcasecmp(folder, "Friends"))
1670                 return 4;
1671         else if (!strcasecmp(folder, "Cust1"))
1672                 return 5;
1673         else if (!strcasecmp(folder, "Cust2"))
1674                 return 6;
1675         else if (!strcasecmp(folder, "Cust3"))
1676                 return 7;
1677         else if (!strcasecmp(folder, "Cust4"))
1678                 return 8;
1679         else if (!strcasecmp(folder, "Cust5"))
1680                 return 9;
1681         else /*assume they meant INBOX if folder is not found otherwise*/
1682                 return 0;
1683 }
1684
1685 /*!
1686  * \brief Gets the number of messages that exist in a mailbox folder.
1687  * \param context
1688  * \param mailbox
1689  * \param folder
1690  * 
1691  * This method is used when IMAP backend is used.
1692  * \return The number of messages in this mailbox folder (zero or more).
1693  */
1694 static int messagecount(const char *context, const char *mailbox, const char *folder)
1695 {
1696         SEARCHPGM *pgm;
1697         SEARCHHEADER *hdr;
1698
1699         struct ast_vm_user *vmu, vmus;
1700         struct vm_state *vms_p;
1701         int ret = 0;
1702         int fold = folder_int(folder);
1703         int urgent = 0;
1704         
1705         if (ast_strlen_zero(mailbox))
1706                 return 0;
1707
1708         /* We have to get the user before we can open the stream! */
1709         vmu = find_user(&vmus, context, mailbox);
1710         if (!vmu) {
1711                 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailbox, context);
1712                 return -1;
1713         } else {
1714                 /* No IMAP account available */
1715                 if (vmu->imapuser[0] == '\0') {
1716                         ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1717                         return -1;
1718                 }
1719         }
1720         
1721         /* No IMAP account available */
1722         if (vmu->imapuser[0] == '\0') {
1723                 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1724                 free_user(vmu);
1725                 return -1;
1726         }
1727
1728         /* check if someone is accessing this box right now... */
1729         vms_p = get_vm_state_by_imapuser(vmu->imapuser,1);
1730         if (!vms_p) {
1731                 vms_p = get_vm_state_by_mailbox(mailbox, context, 1);
1732         }
1733         if (vms_p) {
1734                 ast_debug(3, "Returning before search - user is logged in\n");
1735                 if (fold == 0) { /* INBOX */
1736                         return vms_p->newmessages;
1737                 }
1738                 if (fold == 1) { /* Old messages */
1739                         return vms_p->oldmessages;
1740                 }
1741                 if (fold == 11) {/*Urgent messages*/
1742                         return vms_p->urgentmessages;
1743                 }
1744         }
1745
1746         /* add one if not there... */
1747         vms_p = get_vm_state_by_imapuser(vmu->imapuser,0);
1748         if (!vms_p) {
1749                 vms_p = get_vm_state_by_mailbox(mailbox, context, 0);
1750         }
1751
1752         /* If URGENT, then look at INBOX */
1753         if (fold == 11) {
1754                 fold = NEW_FOLDER;
1755                 urgent = 1;
1756         }
1757
1758         if (!vms_p) {
1759                 ast_debug(3,"Adding new vmstate for %s\n",vmu->imapuser);
1760                 if (!(vms_p = ast_calloc(1, sizeof(*vms_p)))) {
1761                         return -1;
1762                 }
1763                 ast_copy_string(vms_p->imapuser,vmu->imapuser, sizeof(vms_p->imapuser));
1764                 ast_copy_string(vms_p->username, mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
1765                 vms_p->mailstream = NIL; /* save for access from interactive entry point */
1766                 ast_debug(3, "Copied %s to %s\n",vmu->imapuser,vms_p->imapuser);
1767                 vms_p->updated = 1;
1768                 ast_copy_string(vms_p->curbox, mbox(fold), sizeof(vms_p->curbox));
1769                 init_vm_state(vms_p);
1770                 vmstate_insert(vms_p);
1771         }
1772         ret = init_mailstream(vms_p, fold);
1773         if (!vms_p->mailstream) {
1774                 ast_log(AST_LOG_ERROR, "Houston we have a problem - IMAP mailstream is NULL\n");
1775                 return -1;
1776         }
1777         if (ret == 0) {
1778                 pgm = mail_newsearchpgm ();
1779                 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)mailbox);
1780                 pgm->header = hdr;
1781                 if (fold != 1) {
1782                         pgm->unseen = 1;
1783                         pgm->seen = 0;
1784                 }
1785                 /* In the special case where fold is 1 (old messages) we have to do things a bit
1786                  * differently. Old messages are stored in the INBOX but are marked as "seen"
1787                  */
1788                 else {
1789                         pgm->unseen = 0;
1790                         pgm->seen = 1;
1791                 }
1792                 /* look for urgent messages */
1793                 if (urgent) {
1794                         pgm->flagged = 1;
1795                         pgm->unflagged = 0;
1796                 }
1797                 pgm->undeleted = 1;
1798                 pgm->deleted = 0;
1799
1800                 vms_p->vmArrayIndex = 0;
1801                 mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
1802                 if (fold == 0 && urgent == 0)
1803                         vms_p->newmessages = vms_p->vmArrayIndex;
1804                 if (fold == 1)
1805                         vms_p->oldmessages = vms_p->vmArrayIndex;
1806                 if (fold == 0 && urgent == 1)
1807                         vms_p->urgentmessages = vms_p->vmArrayIndex;
1808                 /*Freeing the searchpgm also frees the searchhdr*/
1809                 mail_free_searchpgm(&pgm);
1810                 vms_p->updated = 0;
1811                 return vms_p->vmArrayIndex;
1812         } else {  
1813                 mail_ping(vms_p->mailstream);
1814         }
1815         return 0;
1816 }
1817
1818 static int imap_store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag)
1819 {
1820         char *myserveremail = serveremail;
1821         char fn[PATH_MAX];
1822         char introfn[PATH_MAX];
1823         char mailbox[256];
1824         char *stringp;
1825         FILE *p=NULL;
1826         char tmp[80] = "/tmp/astmail-XXXXXX";
1827         long len;
1828         void *buf;
1829         int tempcopy = 0;
1830         STRING str;
1831         int ret; /* for better error checking */
1832         char *imap_flags = NIL;
1833
1834         /* Set urgent flag for IMAP message */
1835         if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) {
1836                 ast_debug(3, "Setting message flag \\\\FLAGGED.\n");
1837                 imap_flags="\\FLAGGED";
1838         }
1839         
1840         /* Attach only the first format */
1841         fmt = ast_strdupa(fmt);
1842         stringp = fmt;
1843         strsep(&stringp, "|");
1844
1845         if (!ast_strlen_zero(vmu->serveremail))
1846                 myserveremail = vmu->serveremail;
1847
1848         if (msgnum > -1)
1849                 make_file(fn, sizeof(fn), dir, msgnum);
1850         else
1851                 ast_copy_string (fn, dir, sizeof(fn));
1852
1853         snprintf(introfn, sizeof(introfn), "%sintro", fn);
1854         if (ast_fileexists(introfn, NULL, NULL) <= 0) {
1855                 *introfn = '\0';
1856         }
1857         
1858         if (ast_strlen_zero(vmu->email)) {
1859                 /* We need the vmu->email to be set when we call make_email_file, but
1860                  * if we keep it set, a duplicate e-mail will be created. So at the end
1861                  * of this function, we will revert back to an empty string if tempcopy
1862                  * is 1.
1863                  */
1864                 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
1865                 tempcopy = 1;
1866         }
1867
1868         if (!strcmp(fmt, "wav49"))
1869                 fmt = "WAV";
1870         ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
1871
1872         /* Make a temporary file instead of piping directly to sendmail, in case the mail
1873            command hangs. */
1874         if (!(p = vm_mkftemp(tmp))) {
1875                 ast_log(AST_LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
1876                 if (tempcopy)
1877                         *(vmu->email) = '\0';
1878                 return -1;
1879         }
1880
1881         if (msgnum < 0 && imapgreetings) {
1882                 if ((ret = init_mailstream(vms, GREETINGS_FOLDER))) {
1883                         ast_log(AST_LOG_WARNING, "Unable to open mailstream.\n");
1884                         return -1;
1885                 }
1886                 imap_delete_old_greeting(fn, vms);
1887         }
1888         
1889         make_email_file(p, myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, S_OR(chan->cid.cid_num, NULL), S_OR(chan->cid.cid_name, NULL), fn, introfn, fmt, duration, 1, chan, NULL, 1, flag);
1890         /* read mail file to memory */          
1891         len = ftell(p);
1892         rewind(p);
1893         if (!(buf = ast_malloc(len + 1))) {
1894                 ast_log(AST_LOG_ERROR, "Can't allocate %ld bytes to read message\n", len + 1);
1895                 fclose(p);
1896                 if (tempcopy)
1897                         *(vmu->email) = '\0';
1898                 return -1;
1899         }
1900         if (fread(buf, len, 1, p) < len) {
1901                 if (ferror(p)) {
1902                         ast_log(LOG_ERROR, "Short read while reading in mail file.\n");
1903                         return -1;
1904                 }
1905         }
1906         ((char *)buf)[len] = '\0';
1907         INIT(&str, mail_string, buf, len);
1908         ret = init_mailstream(vms, NEW_FOLDER);
1909         if (ret == 0) {
1910                 imap_mailbox_name(mailbox, sizeof(mailbox), vms, NEW_FOLDER, 1);
1911                 if(!mail_append_full(vms->mailstream, mailbox, imap_flags, NIL, &str))
1912                         ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
1913                 fclose(p);
1914                 unlink(tmp);
1915                 ast_free(buf);
1916         } else {
1917                 ast_log(LOG_ERROR, "Could not initialize mailstream for %s\n",mailbox);
1918                 fclose(p);
1919                 unlink(tmp);
1920                 ast_free(buf);
1921                 return -1;
1922         }
1923         ast_debug(3, "%s stored\n", fn);
1924         
1925         if (tempcopy)
1926                 *(vmu->email) = '\0';
1927         
1928         return 0;
1929
1930 }
1931
1932 /*!
1933  * \brief Gets the number of messages that exist in the inbox folder.
1934  * \param mailbox_context
1935  * \param newmsgs The variable that is updated with the count of new messages within this inbox.
1936  * \param oldmsgs The variable that is updated with the count of old messages within this inbox.
1937  * \param urgentmsgs The variable that is updated with the count of urgent messages within this inbox.
1938  * 
1939  * This method is used when IMAP backend is used.
1940  * Simultaneously determines the count of new,old, and urgent messages. The total messages would then be the sum of these three.
1941  *
1942  * \return zero on success, -1 on error.
1943  */
1944
1945 static int inboxcount2(const char *mailbox_context, int *urgentmsgs, int *newmsgs, int *oldmsgs)
1946 {
1947         char tmp[PATH_MAX] = "";
1948         char *mailboxnc;
1949         char *context;
1950         char *mb;
1951         char *cur;
1952         if (newmsgs)
1953                 *newmsgs = 0;
1954         if (oldmsgs)
1955                 *oldmsgs = 0;
1956         if (urgentmsgs)
1957                 *urgentmsgs = 0;
1958
1959         ast_debug(3,"Mailbox is set to %s\n",mailbox_context);
1960         /* If no mailbox, return immediately */
1961         if (ast_strlen_zero(mailbox_context))
1962                 return 0;
1963         
1964         ast_copy_string(tmp, mailbox_context, sizeof(tmp));
1965         context = strchr(tmp, '@');
1966         if (strchr(mailbox_context, ',')) {
1967                 int tmpnew, tmpold, tmpurgent;
1968                 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
1969                 mb = tmp;
1970                 while ((cur = strsep(&mb, ", "))) {
1971                         if (!ast_strlen_zero(cur)) {
1972                                 if (inboxcount2(cur, urgentmsgs ? &tmpurgent : NULL, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
1973                                         return -1;
1974                                 else {
1975                                         if (newmsgs)
1976                                                 *newmsgs += tmpnew; 
1977                                         if (oldmsgs)
1978                                                 *oldmsgs += tmpold;
1979                                         if (urgentmsgs)
1980                                                 *urgentmsgs += tmpurgent;
1981                                 }
1982                         }
1983                 }
1984                 return 0;
1985         }
1986         if (context) {
1987                 *context = '\0';
1988                 mailboxnc = tmp;
1989                 context++;
1990         } else {
1991                 context = "default";
1992                 mailboxnc = (char *)mailbox_context;
1993         }
1994         if (newmsgs) {
1995                 if ((*newmsgs = messagecount(context, mailboxnc, imapfolder)) < 0)
1996                         return -1;
1997         }
1998         if (oldmsgs) {
1999                 if ((*oldmsgs = messagecount(context, mailboxnc, "Old")) < 0)
2000                         return -1;
2001         }
2002         if (urgentmsgs) {
2003                 if((*urgentmsgs = messagecount(context, mailboxnc, "Urgent")) < 0)
2004                         return -1;
2005         }
2006         return 0;
2007 }
2008
2009 static int inboxcount(const char *mailbox_context, int *newmsgs, int *oldmsgs)
2010 {
2011         return inboxcount2(mailbox_context, NULL, newmsgs, oldmsgs);
2012 }
2013
2014 /** 
2015  * \brief Determines if the given folder has messages.
2016  * \param mailbox The @ delimited string for user@context. If no context is found, uses 'default' for the context.
2017  * \param folder the folder to look in
2018  *
2019  * This function is used when the mailbox is stored in an IMAP back end.
2020  * This invokes the messagecount(). Here we are interested in the presence of messages (> 0) only, not the actual count.
2021  * \return 1 if the folder has one or more messages. zero otherwise.
2022  */
2023
2024 static int has_voicemail(const char *mailbox, const char *folder)
2025 {
2026         char tmp[256], *tmp2, *box, *context;
2027         ast_copy_string(tmp, mailbox, sizeof(tmp));
2028         tmp2 = tmp;
2029         if (strchr(tmp2, ',')) {
2030                 while ((box = strsep(&tmp2, ","))) {
2031                         if (!ast_strlen_zero(box)) {
2032                                 if (has_voicemail(box, folder))
2033                                         return 1;
2034                         }
2035                 }
2036         }
2037         if ((context= strchr(tmp, '@')))
2038                 *context++ = '\0';
2039         else
2040                 context = "default";
2041         return messagecount(context, tmp, folder) ? 1 : 0;
2042 }
2043
2044 /*!
2045  * \brief Copies a message from one mailbox to another.
2046  * \param chan
2047  * \param vmu
2048  * \param imbox
2049  * \param msgnum
2050  * \param duration
2051  * \param recip
2052  * \param fmt
2053  * \param dir
2054  *
2055  * This works with IMAP storage based mailboxes.
2056  *
2057  * \return zero on success, -1 on error.
2058  */
2059 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)
2060 {
2061         struct vm_state *sendvms = NULL, *destvms = NULL;
2062         char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
2063         if (msgnum >= recip->maxmsg) {
2064                 ast_log(LOG_WARNING, "Unable to copy mail, mailbox %s is full\n", recip->mailbox);
2065                 return -1;
2066         }
2067         if (!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 0))) {
2068                 ast_log(LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
2069                 return -1;
2070         }
2071         if (!(destvms = get_vm_state_by_imapuser(recip->imapuser, 0))) {
2072                 ast_log(LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
2073                 return -1;
2074         }
2075         snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
2076         if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(imbox)) == T))
2077                 return 0;
2078         ast_log(LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
2079         return -1;
2080 }
2081
2082 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int use_folder)
2083 {
2084         char tmp[256], *t = tmp;
2085         size_t left = sizeof(tmp);
2086         
2087         if (box == OLD_FOLDER) {
2088                 ast_copy_string(vms->curbox, mbox(NEW_FOLDER), sizeof(vms->curbox));
2089         } else {
2090                 ast_copy_string(vms->curbox, mbox(box), sizeof(vms->curbox));
2091         }
2092
2093         if (box == NEW_FOLDER) {
2094                 ast_copy_string(vms->vmbox, "vm-INBOX", sizeof(vms->vmbox));
2095         } else {
2096                 snprintf(vms->vmbox, sizeof(vms->vmbox), "vm-%s", mbox(box));
2097         }
2098
2099         /* Build up server information */
2100         ast_build_string(&t, &left, "{%s:%s/imap", imapserver, imapport);
2101
2102         /* Add authentication user if present */
2103         if (!ast_strlen_zero(authuser))
2104                 ast_build_string(&t, &left, "/authuser=%s", authuser);
2105
2106         /* Add flags if present */
2107         if (!ast_strlen_zero(imapflags))
2108                 ast_build_string(&t, &left, "/%s", imapflags);
2109
2110         /* End with username */
2111         ast_build_string(&t, &left, "/user=%s}", vms->imapuser);
2112         if (box == NEW_FOLDER || box == OLD_FOLDER)
2113                 snprintf(spec, len, "%s%s", tmp, use_folder? imapfolder: "INBOX");
2114         else if (box == GREETINGS_FOLDER)
2115                 snprintf(spec, len, "%s%s", tmp, greetingfolder);
2116         else {  /* Other folders such as Friends, Family, etc... */
2117                 if (!ast_strlen_zero(imapparentfolder)) {
2118                         /* imapparentfolder would typically be set to INBOX */
2119                         snprintf(spec, len, "%s%s%c%s", tmp, imapparentfolder, delimiter, mbox(box));
2120                 } else {
2121                         snprintf(spec, len, "%s%s", tmp, mbox(box));
2122                 }
2123         }
2124 }
2125
2126 static int init_mailstream(struct vm_state *vms, int box)
2127 {
2128         MAILSTREAM *stream = NIL;
2129         long debug;
2130         char tmp[256];
2131         
2132         if (!vms) {
2133                 ast_log (LOG_ERROR,"vm_state is NULL!\n");
2134                 return -1;
2135         }
2136         if (option_debug > 2)
2137                 ast_log (LOG_DEBUG,"vm_state user is:%s\n",vms->imapuser);
2138         if (vms->mailstream == NIL || !vms->mailstream) {
2139                 if (option_debug)
2140                         ast_log (LOG_DEBUG,"mailstream not set.\n");
2141         } else {
2142                 stream = vms->mailstream;
2143         }
2144         /* debug = T;  user wants protocol telemetry? */
2145         debug = NIL;  /* NO protocol telemetry? */
2146
2147         if (delimiter == '\0') {                /* did not probe the server yet */
2148                 char *cp;
2149 #ifdef USE_SYSTEM_IMAP
2150 #include <imap/linkage.c>
2151 #elif defined(USE_SYSTEM_CCLIENT)
2152 #include <c-client/linkage.c>
2153 #else
2154 #include "linkage.c"
2155 #endif
2156                 /* Connect to INBOX first to get folders delimiter */
2157                 imap_mailbox_name(tmp, sizeof(tmp), vms, 0, 1);
2158                 ast_mutex_lock(&vms->lock);
2159                 stream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2160                 ast_mutex_unlock(&vms->lock);
2161                 if (stream == NIL) {
2162                         ast_log (LOG_ERROR, "Can't connect to imap server %s\n", tmp);
2163                         return -1;
2164                 }
2165                 get_mailbox_delimiter(stream);
2166                 /* update delimiter in imapfolder */
2167                 for (cp = imapfolder; *cp; cp++)
2168                         if (*cp == '/')
2169                                 *cp = delimiter;
2170         }
2171         /* Now connect to the target folder */
2172         imap_mailbox_name(tmp, sizeof(tmp), vms, box, 1);
2173         if (option_debug > 2)
2174                 ast_log (LOG_DEBUG,"Before mail_open, server: %s, box:%d\n", tmp, box);
2175         ast_mutex_lock(&vms->lock);
2176         vms->mailstream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2177         ast_mutex_unlock(&vms->lock);
2178         if (vms->mailstream == NIL) {
2179                 return -1;
2180         } else {
2181                 return 0;
2182         }
2183 }
2184
2185 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box)
2186 {
2187         SEARCHPGM *pgm;
2188         SEARCHHEADER *hdr;
2189         int ret, urgent = 0;
2190
2191         /* If Urgent, then look at INBOX */
2192         if (box == 11) {
2193                 box = NEW_FOLDER;
2194                 urgent = 1;
2195         }
2196
2197         ast_copy_string(vms->imapuser,vmu->imapuser, sizeof(vms->imapuser));
2198         ast_debug(3,"Before init_mailstream, user is %s\n",vmu->imapuser);
2199
2200         if ((ret = init_mailstream(vms, box)) || !vms->mailstream) {
2201                 ast_log(AST_LOG_ERROR, "Could not initialize mailstream\n");
2202                 return -1;
2203         }
2204         
2205         create_dirpath(vms->curdir, sizeof(vms->curdir), vmu->context, vms->username, vms->curbox);
2206         
2207         /* Check Quota */
2208         if  (box == 0)  {
2209                 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mbox(box));
2210                 check_quota(vms,(char *)mbox(box));
2211         }
2212
2213         pgm = mail_newsearchpgm();
2214
2215         /* Check IMAP folder for Asterisk messages only... */
2216         hdr = mail_newsearchheader("X-Asterisk-VM-Extension", vmu->mailbox);
2217         pgm->header = hdr;
2218         pgm->deleted = 0;
2219         pgm->undeleted = 1;
2220
2221         /* if box = NEW_FOLDER, check for new, if box = OLD_FOLDER, check for read */
2222         if (box == NEW_FOLDER && urgent == 1) {
2223                 pgm->unseen = 1;
2224                 pgm->seen = 0;
2225                 pgm->flagged = 1;
2226                 pgm->unflagged = 0;
2227         } else if (box == NEW_FOLDER && urgent == 0) {
2228                 pgm->unseen = 1;
2229                 pgm->seen = 0;
2230                 pgm->flagged = 0;
2231                 pgm->unflagged = 1;
2232         } else if (box == OLD_FOLDER) {
2233                 pgm->seen = 1;
2234                 pgm->unseen = 0;
2235         }
2236
2237         ast_debug(3,"Before mail_search_full, user is %s\n",vmu->imapuser);
2238
2239         vms->vmArrayIndex = 0;
2240         mail_search_full (vms->mailstream, NULL, pgm, NIL);
2241         vms->lastmsg = vms->vmArrayIndex - 1;
2242         mail_free_searchpgm(&pgm);
2243
2244         return 0;
2245 }
2246
2247 static void write_file(char *filename, char *buffer, unsigned long len)
2248 {
2249         FILE *output;
2250
2251         output = fopen (filename, "w");
2252         if (fwrite(buffer, len, 1, output) < len) {
2253                 if (ferror(output)) {
2254                         ast_log(LOG_ERROR, "Short write while writing e-mail body.\n");
2255                 }
2256         }
2257         fclose (output);
2258 }
2259
2260 static void update_messages_by_imapuser(const char *user, unsigned long number)
2261 {
2262         struct vmstate *vlist = NULL;
2263
2264         AST_LIST_LOCK(&vmstates);
2265         AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2266                 if (!vlist->vms) {
2267                         ast_debug(3, "error: vms is NULL for %s\n", user);
2268                         continue;
2269                 }
2270                 if (!vlist->vms->imapuser) {
2271                         ast_debug(3, "error: imapuser is NULL for %s\n", user);
2272                         continue;
2273                 }
2274                 ast_debug(3, "saving mailbox message number %lu as message %d. Interactive set to %d\n", number, vlist->vms->vmArrayIndex, vlist->vms->interactive);
2275                 vlist->vms->msgArray[vlist->vms->vmArrayIndex++] = number;
2276         }
2277         AST_LIST_UNLOCK(&vmstates);
2278 }
2279
2280 void mm_searched(MAILSTREAM *stream, unsigned long number)
2281 {
2282         char *mailbox = stream->mailbox, buf[1024] = "", *user;
2283
2284         if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))))
2285                 return;
2286
2287         update_messages_by_imapuser(user, number);
2288 }
2289
2290 static struct ast_vm_user *find_user_realtime_imapuser(const char *imapuser)
2291 {
2292         struct ast_variable *var;
2293         struct ast_vm_user *vmu;
2294
2295         vmu = ast_calloc(1, sizeof *vmu);
2296         if (!vmu)
2297                 return NULL;
2298         ast_set_flag(vmu, VM_ALLOCED);
2299         populate_defaults(vmu);
2300
2301         var = ast_load_realtime("voicemail", "imapuser", imapuser, NULL);
2302         if (var) {
2303                 apply_options_full(vmu, var);
2304                 ast_variables_destroy(var);
2305                 return vmu;
2306         } else {
2307                 free(vmu);
2308                 return NULL;
2309         }
2310 }
2311
2312 /* Interfaces to C-client */
2313
2314 void mm_exists(MAILSTREAM * stream, unsigned long number)
2315 {
2316         /* mail_ping will callback here if new mail! */
2317         ast_debug(4, "Entering EXISTS callback for message %ld\n", number);
2318         if (number == 0) return;
2319         set_update(stream);
2320 }
2321
2322
2323 void mm_expunged(MAILSTREAM * stream, unsigned long number)
2324 {
2325         /* mail_ping will callback here if expunged mail! */
2326         ast_debug(4, "Entering EXPUNGE callback for message %ld\n", number);
2327         if (number == 0) return;
2328         set_update(stream);
2329 }
2330
2331
2332 void mm_flags(MAILSTREAM * stream, unsigned long number)
2333 {
2334         /* mail_ping will callback here if read mail! */
2335         ast_debug(4, "Entering FLAGS callback for message %ld\n", number);
2336         if (number == 0) return;
2337         set_update(stream);
2338 }
2339
2340
2341 void mm_notify(MAILSTREAM * stream, char *string, long errflg)
2342 {
2343         ast_debug(5, "Entering NOTIFY callback, errflag is %ld, string is %s\n", errflg, string);
2344         mm_log (string, errflg);
2345 }
2346
2347
2348 void mm_list(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2349 {
2350         if (delimiter == '\0') {
2351                 delimiter = delim;
2352         }
2353
2354         ast_debug(5, "Delimiter set to %c and mailbox %s\n",delim, mailbox);
2355         if (attributes & LATT_NOINFERIORS)
2356                 ast_debug(5, "no inferiors\n");
2357         if (attributes & LATT_NOSELECT)
2358                 ast_debug(5, "no select\n");
2359         if (attributes & LATT_MARKED)
2360                 ast_debug(5, "marked\n");
2361         if (attributes & LATT_UNMARKED)
2362                 ast_debug(5, "unmarked\n");
2363 }
2364
2365
2366 void mm_lsub(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2367 {
2368         ast_debug(5, "Delimiter set to %c and mailbox %s\n",delim, mailbox);
2369         if (attributes & LATT_NOINFERIORS)
2370                 ast_debug(5, "no inferiors\n");
2371         if (attributes & LATT_NOSELECT)
2372                 ast_debug(5, "no select\n");
2373         if (attributes & LATT_MARKED)
2374                 ast_debug(5, "marked\n");
2375         if (attributes & LATT_UNMARKED)
2376                 ast_debug(5, "unmarked\n");
2377 }
2378
2379
2380 void mm_status(MAILSTREAM * stream, char *mailbox, MAILSTATUS * status)
2381 {
2382         ast_log(AST_LOG_NOTICE, " Mailbox %s", mailbox);
2383         if (status->flags & SA_MESSAGES)
2384                 ast_log(AST_LOG_NOTICE, ", %lu messages", status->messages);
2385         if (status->flags & SA_RECENT)
2386                 ast_log(AST_LOG_NOTICE, ", %lu recent", status->recent);
2387         if (status->flags & SA_UNSEEN)
2388                 ast_log(AST_LOG_NOTICE, ", %lu unseen", status->unseen);
2389         if (status->flags & SA_UIDVALIDITY)
2390                 ast_log(AST_LOG_NOTICE, ", %lu UID validity", status->uidvalidity);
2391         if (status->flags & SA_UIDNEXT)
2392                 ast_log(AST_LOG_NOTICE, ", %lu next UID", status->uidnext);
2393         ast_log(AST_LOG_NOTICE, "\n");
2394 }
2395
2396
2397 void mm_log(char *string, long errflg)
2398 {
2399         switch ((short) errflg) {
2400                 case NIL:
2401                         ast_debug(1,"IMAP Info: %s\n", string);
2402                         break;
2403                 case PARSE:
2404                 case WARN:
2405                         ast_log(AST_LOG_WARNING, "IMAP Warning: %s\n", string);
2406                         break;
2407                 case ERROR:
2408                         ast_log(AST_LOG_ERROR, "IMAP Error: %s\n", string);
2409                         break;
2410         }
2411 }
2412
2413
2414 void mm_dlog(char *string)
2415 {
2416         ast_log(AST_LOG_NOTICE, "%s\n", string);
2417 }
2418
2419
2420 void mm_login(NETMBX * mb, char *user, char *pwd, long trial)
2421 {
2422         struct ast_vm_user *vmu;
2423
2424         ast_debug(4, "Entering callback mm_login\n");
2425
2426         ast_copy_string(user, mb->user, MAILTMPLEN);
2427
2428         /* We should only do this when necessary */
2429         if (!ast_strlen_zero(authpassword)) {
2430                 ast_copy_string(pwd, authpassword, MAILTMPLEN);
2431         } else {
2432                 AST_LIST_TRAVERSE(&users, vmu, list) {
2433                         if (!strcasecmp(mb->user, vmu->imapuser)) {
2434                                 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2435                                 break;
2436                         }
2437                 }
2438                 if (!vmu) {
2439                         if ((vmu = find_user_realtime_imapuser(mb->user))) {
2440                                 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2441                                 free_user(vmu);
2442                         }
2443                 }
2444         }
2445 }
2446
2447
2448 void mm_critical(MAILSTREAM * stream)
2449 {
2450 }
2451
2452
2453 void mm_nocritical(MAILSTREAM * stream)
2454 {
2455 }
2456
2457
2458 long mm_diskerror(MAILSTREAM * stream, long errcode, long serious)
2459 {
2460         kill (getpid (), SIGSTOP);
2461         return NIL;
2462 }
2463
2464
2465 void mm_fatal(char *string)
2466 {
2467         ast_log(AST_LOG_ERROR, "IMAP access FATAL error: %s\n", string);
2468 }
2469
2470 /* C-client callback to handle quota */
2471 static void mm_parsequota(MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
2472 {
2473         struct vm_state *vms;
2474         char *mailbox = stream->mailbox, *user;
2475         char buf[1024] = "";
2476         unsigned long usage = 0, limit = 0;
2477         
2478         while (pquota) {
2479                 usage = pquota->usage;
2480                 limit = pquota->limit;
2481                 pquota = pquota->next;
2482         }
2483         
2484         if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || !(vms = get_vm_state_by_imapuser(user, 2))) {
2485                 ast_log(AST_LOG_ERROR, "No state found.\n");
2486                 return;
2487         }
2488
2489         ast_debug(3, "User %s usage is %lu, limit is %lu\n", user, usage, limit);
2490
2491         vms->quota_usage = usage;
2492         vms->quota_limit = limit;
2493 }
2494
2495 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len)
2496 {
2497         char *start, *eol_pnt;
2498         int taglen;
2499
2500         if (ast_strlen_zero(header) || ast_strlen_zero(tag))
2501                 return NULL;
2502
2503         taglen = strlen(tag) + 1;
2504         if (taglen < 1)
2505                 return NULL;
2506
2507         if (!(start = strstr(header, tag)))
2508                 return NULL;
2509
2510         /* Since we can be called multiple times we should clear our buffer */
2511         memset(buf, 0, len);
2512
2513         ast_copy_string(buf, start+taglen, len);
2514         if ((eol_pnt = strchr(buf,'\r')) || (eol_pnt = strchr(buf,'\n')))
2515                 *eol_pnt = '\0';
2516         return buf;
2517 }
2518
2519 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len)
2520 {
2521         char *start, *quote, *eol_pnt;
2522
2523         if (ast_strlen_zero(mailbox))
2524                 return NULL;
2525
2526         if (!(start = strstr(mailbox, "/user=")))
2527                 return NULL;
2528
2529         ast_copy_string(buf, start+6, len);
2530
2531         if (!(quote = strchr(buf, '\"'))) {
2532                 if (!(eol_pnt = strchr(buf, '/')))
2533                         eol_pnt = strchr(buf,'}');
2534                 *eol_pnt = '\0';
2535                 return buf;
2536         } else {
2537                 eol_pnt = strchr(buf+1,'\"');
2538                 *eol_pnt = '\0';
2539                 return buf+1;
2540         }
2541 }
2542
2543 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu)
2544 {
2545         struct vm_state *vms_p;
2546
2547         if (option_debug > 4)
2548                 ast_log(AST_LOG_DEBUG,"Adding new vmstate for %s\n",vmu->imapuser);
2549         if (!(vms_p = ast_calloc(1, sizeof(*vms_p))))
2550                 return NULL;
2551         ast_copy_string(vms_p->imapuser, vmu->imapuser, sizeof(vms_p->imapuser));
2552         ast_copy_string(vms_p->username, vmu->mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
2553         ast_copy_string(vms_p->context, vmu->context, sizeof(vms_p->context));
2554         vms_p->mailstream = NIL; /* save for access from interactive entry point */
2555         if (option_debug > 4)
2556                 ast_log(AST_LOG_DEBUG,"Copied %s to %s\n",vmu->imapuser,vms_p->imapuser);
2557         vms_p->updated = 1;
2558         /* set mailbox to INBOX! */
2559         ast_copy_string(vms_p->curbox, mbox(0), sizeof(vms_p->curbox));
2560         init_vm_state(vms_p);
2561         vmstate_insert(vms_p);
2562         return vms_p;
2563 }
2564
2565 static struct vm_state *get_vm_state_by_imapuser(char *user, int interactive)
2566 {
2567         struct vmstate *vlist = NULL;
2568
2569         AST_LIST_LOCK(&vmstates);
2570         AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2571                 if (!vlist->vms) {
2572                         ast_debug(3, "error: vms is NULL for %s\n", user);
2573                         continue;
2574                 }
2575                 if (!vlist->vms->imapuser) {
2576                         ast_debug(3, "error: imapuser is NULL for %s\n", user);
2577                         continue;
2578                 }
2579
2580                 if (!strcmp(vlist->vms->imapuser, user) && (interactive == 2 || vlist->vms->interactive == interactive)) {
2581                         AST_LIST_UNLOCK(&vmstates);
2582                         return vlist->vms;
2583                 }
2584         }
2585         AST_LIST_UNLOCK(&vmstates);
2586
2587         ast_debug(3, "%s not found in vmstates\n", user);
2588
2589         return NULL;
2590 }
2591
2592 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive)
2593 {
2594
2595         struct vmstate *vlist = NULL;
2596         const char *local_context = S_OR(context, "default");
2597
2598         AST_LIST_LOCK(&vmstates);
2599         AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2600                 if (!vlist->vms) {
2601                         ast_debug(3, "error: vms is NULL for %s\n", mailbox);
2602                         continue;
2603                 }
2604                 if (!vlist->vms->username || !vlist->vms->context) {
2605                         ast_debug(3, "error: username is NULL for %s\n", mailbox);
2606                         continue;
2607                 }
2608
2609                 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);
2610                 
2611                 if (!strcmp(vlist->vms->username,mailbox) && !strcmp(vlist->vms->context, local_context) && vlist->vms->interactive == interactive) {
2612                         ast_debug(3, "Found it!\n");
2613                         AST_LIST_UNLOCK(&vmstates);
2614                         return vlist->vms;
2615                 }
2616         }
2617         AST_LIST_UNLOCK(&vmstates);
2618
2619         ast_debug(3, "%s not found in vmstates\n", mailbox);
2620
2621         return NULL;
2622 }
2623
2624 static void vmstate_insert(struct vm_state *vms) 
2625 {
2626         struct vmstate *v;
2627         struct vm_state *altvms;
2628
2629         /* If interactive, it probably already exists, and we should
2630            use the one we already have since it is more up to date.
2631            We can compare the username to find the duplicate */
2632         if (vms->interactive == 1) {
2633                 altvms = get_vm_state_by_mailbox(vms->username, vms->context, 0);
2634                 if (altvms) {   
2635                         ast_debug(3, "Duplicate mailbox %s, copying message info...\n",vms->username);
2636                         vms->newmessages = altvms->newmessages;
2637                         vms->oldmessages = altvms->oldmessages;
2638                         vms->vmArrayIndex = altvms->vmArrayIndex;
2639                         vms->lastmsg = altvms->lastmsg;
2640                         vms->curmsg = altvms->curmsg;
2641                         /* get a pointer to the persistent store */
2642                         vms->persist_vms = altvms;
2643                         /* Reuse the mailstream? */
2644                         vms->mailstream = altvms->mailstream;
2645                         /* vms->mailstream = NIL; */
2646                 }
2647         }
2648
2649         if (!(v = ast_calloc(1, sizeof(*v))))
2650                 return;
2651         
2652         v->vms = vms;
2653
2654         ast_debug(3, "Inserting vm_state for user:%s, mailbox %s\n",vms->imapuser,vms->username);
2655
2656         AST_LIST_LOCK(&vmstates);
2657         AST_LIST_INSERT_TAIL(&vmstates, v, list);
2658         AST_LIST_UNLOCK(&vmstates);
2659 }
2660
2661 static void vmstate_delete(struct vm_state *vms) 
2662 {
2663         struct vmstate *vc = NULL;
2664         struct vm_state *altvms = NULL;
2665
2666         /* If interactive, we should copy pertinent info
2667            back to the persistent state (to make update immediate) */
2668         if (vms->interactive == 1 && (altvms = vms->persist_vms)) {
2669                 ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
2670                 altvms->newmessages = vms->newmessages;
2671                 altvms->oldmessages = vms->oldmessages;
2672                 altvms->updated = 1;
2673         }
2674         
2675         ast_debug(3, "Removing vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2676         
2677         AST_LIST_LOCK(&vmstates);
2678         AST_LIST_TRAVERSE_SAFE_BEGIN(&vmstates, vc, list) {
2679                 if (vc->vms == vms) {
2680                         AST_LIST_REMOVE_CURRENT(list);
2681                         break;
2682                 }
2683         }
2684         AST_LIST_TRAVERSE_SAFE_END
2685         AST_LIST_UNLOCK(&vmstates);
2686         
2687         if (vc) {
2688                 ast_mutex_destroy(&vc->vms->lock);
2689                 ast_free(vc);
2690         }
2691         else
2692                 ast_log(AST_LOG_ERROR, "No vmstate found for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2693 }
2694
2695 static void set_update(MAILSTREAM * stream) 
2696 {
2697         struct vm_state *vms;
2698         char *mailbox = stream->mailbox, *user;
2699         char buf[1024] = "";
2700
2701         if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || !(vms = get_vm_state_by_imapuser(user, 0))) {
2702                 if (user && option_debug > 2)
2703                         ast_log(AST_LOG_WARNING, "User %s mailbox not found for update.\n", user);
2704                 return;
2705         }
2706
2707         ast_debug(3, "User %s mailbox set for update.\n", user);
2708
2709         vms->updated = 1; /* Set updated flag since mailbox changed */
2710 }
2711
2712 static void init_vm_state(struct vm_state *vms) 
2713 {
2714         int x;
2715         vms->vmArrayIndex = 0;
2716         for (x = 0; x < VMSTATE_MAX_MSG_ARRAY; x++) {
2717                 vms->msgArray[x] = 0;
2718         }
2719         ast_mutex_init(&vms->lock);
2720 }
2721
2722 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro) 
2723 {
2724         char *body_content;
2725         char *body_decoded;
2726         char *fn = is_intro ? vms->introfn : vms->fn;
2727         unsigned long len;
2728         unsigned long newlen;
2729         char filename[256];
2730         
2731         if (!body || body == NIL)
2732                 return -1;
2733
2734         body_content = mail_fetchbody(vms->mailstream, vms->msgArray[vms->curmsg], section, &len);
2735         if (body_content != NIL) {
2736                 snprintf(filename, sizeof(filename), "%s.%s", fn, format);
2737                 /* ast_debug(1,body_content); */
2738                 body_decoded = rfc822_base64((unsigned char *)body_content, len, &newlen);
2739                 /* If the body of the file is empty, return an error */
2740                 if (!newlen) {
2741                         return -1;
2742                 }
2743                 write_file(filename, (char *) body_decoded, newlen);
2744         } else {
2745                 ast_debug(5, "Body of message is NULL.\n");
2746                 return -1;
2747         }
2748         return 0;
2749 }
2750
2751 /*! 
2752  * \brief Get delimiter via mm_list callback 
2753  * \param stream
2754  *
2755  * Determines the delimiter character that is used by the underlying IMAP based mail store.
2756  */
2757 static void get_mailbox_delimiter(MAILSTREAM *stream) {
2758         char tmp[50];
2759         snprintf(tmp, sizeof(tmp), "{%s}", imapserver);
2760         mail_list(stream, tmp, "*");
2761 }
2762
2763 /*! 
2764  * \brief Check Quota for user 
2765  * \param vms a pointer to a vm_state struct, will use the mailstream property of this.
2766  * \param mailbox the mailbox to check the quota for.
2767  *
2768  * Calls imap_getquotaroot, which will populate its results into the vm_state vms input structure.
2769  */
2770 static void check_quota(struct vm_state *vms, char *mailbox) {
2771         mail_parameters(NULL, SET_QUOTA, (void *) mm_parsequota);
2772         ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mailbox);
2773         if (vms && vms->mailstream != NULL) {
2774                 imap_getquotaroot(vms->mailstream, mailbox);
2775         } else {
2776                 ast_log(AST_LOG_WARNING, "Mailstream not available for mailbox: %s\n", mailbox);
2777         }
2778 }
2779
2780 #endif /* IMAP_STORAGE */
2781
2782 /*! \brief Lock file path
2783     only return failure if ast_lock_path returns 'timeout',
2784    not if the path does not exist or any other reason
2785 */
2786 static int vm_lock_path(const char *path)
2787 {
2788         switch (ast_lock_path(path)) {
2789         case AST_LOCK_TIMEOUT:
2790                 return -1;
2791         default:
2792                 return 0;
2793         }
2794 }
2795
2796
2797 #ifdef ODBC_STORAGE
2798 struct generic_prepare_struct {
2799         char *sql;
2800         int argc;
2801         char **argv;
2802 };
2803
2804 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
2805 {
2806         struct generic_prepare_struct *gps = data;
2807         int res, i;
2808         SQLHSTMT stmt;
2809
2810         res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
2811         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2812                 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
2813                 return NULL;
2814         }
2815         res = SQLPrepare(stmt, (unsigned char *)gps->sql, SQL_NTS);
2816         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2817                 ast_log(AST_LOG_WARNING, "SQL Prepare failed![%s]\n", gps->sql);
2818                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
2819                 return NULL;
2820         }
2821         for (i = 0; i < gps->argc; i++)
2822                 SQLBindParameter(stmt, i + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->argv[i]), 0, gps->argv[i], 0, NULL);
2823
2824         return stmt;
2825 }
2826
2827 /*!
2828  * \brief Retrieves a file from an ODBC data store.
2829  * \param dir the path to the file to be retreived.
2830  * \param msgnum the message number, such as within a mailbox folder.
2831  * 
2832  * This method is used by the RETRIEVE macro when mailboxes are stored in an ODBC back end.
2833  * 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.
2834  *
2835  * The file is looked up by invoking a SQL on the odbc_table (default 'voicemessages') using the dir and msgnum input parameters.
2836  * The output is the message information file with the name msgnum and the extension .txt
2837  * and the message file with the extension of its format, in the directory with base file name of the msgnum.
2838  * 
2839  * \return 0 on success, -1 on error.
2840  */
2841 static int retrieve_file(char *dir, int msgnum)
2842 {
2843         int x = 0;
2844         int res;
2845         int fd=-1;
2846         size_t fdlen = 0;
2847         void *fdm = MAP_FAILED;
2848         SQLSMALLINT colcount=0;
2849         SQLHSTMT stmt;
2850         char sql[PATH_MAX];
2851         char fmt[80]="";
2852         char *c;
2853         char coltitle[256];
2854         SQLSMALLINT collen;
2855         SQLSMALLINT datatype;
2856         SQLSMALLINT decimaldigits;
2857         SQLSMALLINT nullable;
2858         SQLULEN colsize;
2859         SQLLEN colsize2;
2860         FILE *f=NULL;
2861         char rowdata[80];
2862         char fn[PATH_MAX];
2863         char full_fn[PATH_MAX];
2864         char msgnums[80];
2865         char *argv[] = { dir, msgnums };
2866         struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
2867
2868         struct odbc_obj *obj;
2869         obj = ast_odbc_request_obj(odbc_database, 0);
2870         if (obj) {
2871                 ast_copy_string(fmt, vmfmts, sizeof(fmt));
2872                 c = strchr(fmt, '|');
2873                 if (c)
2874                         *c = '\0';
2875                 if (!strcasecmp(fmt, "wav49"))
2876                         strcpy(fmt, "WAV");
2877                 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
2878                 if (msgnum > -1)
2879                         make_file(fn, sizeof(fn), dir, msgnum);
2880                 else
2881                         ast_copy_string(fn, dir, sizeof(fn));
2882
2883                 /* Create the information file */
2884                 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
2885                 
2886                 if (!(f = fopen(full_fn, "w+"))) {
2887                         ast_log(AST_LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
2888                         goto yuck;
2889                 }
2890                 
2891                 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
2892                 snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?",odbc_table);
2893                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2894                 if (!stmt) {
2895                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2896                         ast_odbc_release_obj(obj);
2897                         goto yuck;
2898                 }
2899                 res = SQLFetch(stmt);
2900                 if (res == SQL_NO_DATA) {
2901                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2902                         ast_odbc_release_obj(obj);
2903                         goto yuck;
2904                 } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2905                         ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2906                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2907                         ast_odbc_release_obj(obj);
2908                         goto yuck;
2909                 }
2910                 fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
2911                 if (fd < 0) {
2912                         ast_log(AST_LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
2913                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2914                         ast_odbc_release_obj(obj);
2915                         goto yuck;
2916                 }
2917                 res = SQLNumResultCols(stmt, &colcount);
2918                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {   
2919                         ast_log(AST_LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
2920                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2921                         ast_odbc_release_obj(obj);
2922                         goto yuck;
2923                 }
2924                 if (f) 
2925                         fprintf(f, "[message]\n");
2926                 for (x=0;x<colcount;x++) {
2927                         rowdata[0] = '\0';
2928                         collen = sizeof(coltitle);
2929                         res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen, 
2930                                                 &datatype, &colsize, &decimaldigits, &nullable);
2931                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2932                                 ast_log(AST_LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
2933                                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2934                                 ast_odbc_release_obj(obj);
2935                                 goto yuck;
2936                         }
2937                         if (!strcasecmp(coltitle, "recording")) {
2938                                 off_t offset;
2939                                 res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
2940                                 fdlen = colsize2;
2941                                 if (fd > -1) {
2942                                         char tmp[1]="";
2943                                         lseek(fd, fdlen - 1, SEEK_SET);
2944                                         if (write(fd, tmp, 1) != 1) {
2945                                                 close(fd);
2946                                                 fd = -1;
2947                                                 continue;
2948                                         }
2949                                         /* Read out in small chunks */
2950                                         for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
2951                                                 if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
2952                                                         ast_log(AST_LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
2953                                                         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
2954                                                         ast_odbc_release_obj(obj);
2955                                                         goto yuck;
2956                                                 } else {
2957                                                         res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
2958                                                         munmap(fdm, CHUNKSIZE);
2959                                                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2960                                                                 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2961                                                                 unlink(full_fn);
2962                                                                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
2963                                                                 ast_odbc_release_obj(obj);
2964                                                                 goto yuck;
2965                                                         }
2966                                                 }
2967                                         }
2968                                         if (truncate(full_fn, fdlen) < 0) {
2969                                                 ast_log(LOG_WARNING, "Unable to truncate '%s': %s\n", full_fn, strerror(errno));
2970                                         }
2971                                 }
2972                         } else {
2973                                 res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2974                                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2975                                         ast_log(AST_LOG_WARNING, "SQL Get Data error! coltitle=%s\n[%s]\n\n", coltitle, sql);
2976                                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2977                                         ast_odbc_release_obj(obj);
2978                                         goto yuck;
2979                                 }
2980                                 if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
2981                                         fprintf(f, "%s=%s\n", coltitle, rowdata);
2982                         }
2983                 }
2984                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2985                 ast_odbc_release_obj(obj);
2986         } else
2987                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2988 yuck:   
2989         if (f)
2990                 fclose(f);
2991         if (fd > -1)
2992                 close(fd);
2993         return x - 1;
2994 }
2995
2996 /*!
2997  * \brief Determines the highest message number in use for a given user and mailbox folder.
2998  * \param vmu 
2999  * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
3000  *
3001  * This method is used when mailboxes are stored in an ODBC back end.
3002  * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
3003  *
3004  * \return the value of zero or greaterto indicate the last message index in use, -1 to indicate none.
3005  */
3006 static int last_message_index(struct ast_vm_user *vmu, char *dir)
3007 {
3008         int x = 0;
3009         int res;
3010         SQLHSTMT stmt;
3011         char sql[PATH_MAX];
3012         char rowdata[20];
3013         char *argv[] = { dir };
3014         struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
3015
3016         struct odbc_obj *obj;
3017         obj = ast_odbc_request_obj(odbc_database, 0);
3018         if (obj) {
3019                 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?",odbc_table);
3020                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3021                 if (!stmt) {
3022                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3023                         ast_odbc_release_obj(obj);
3024                         goto yuck;
3025                 }
3026                 res = SQLFetch(stmt);
3027                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3028                         ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3029                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3030                         ast_odbc_release_obj(obj);
3031                         goto yuck;
3032                 }
3033                 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3034                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3035                         ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3036                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3037                         ast_odbc_release_obj(obj);
3038                         goto yuck;
3039                 }
3040                 if (sscanf(rowdata, "%d", &x) != 1)
3041                         ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
3042                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3043                 ast_odbc_release_obj(obj);
3044         } else
3045                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3046 yuck:   
3047         return x - 1;
3048 }
3049
3050 /*!
3051  * \brief Determines if the specified message exists.
3052  * \param dir the folder the mailbox folder to look for messages. 
3053  * \param msgnum the message index to query for.
3054  *
3055  * This method is used when mailboxes are stored in an ODBC back end.
3056  *
3057  * \return greater than zero if the message exists, zero when the message does not exist or on error.
3058  */
3059 static int message_exists(char *dir, int msgnum)
3060 {
3061         int x = 0;
3062         int res;
3063         SQLHSTMT stmt;
3064         char sql[PATH_MAX];
3065         char rowdata[20];
3066         char msgnums[20];
3067         char *argv[] = { dir, msgnums };
3068         struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3069
3070         struct odbc_obj *obj;
3071         obj = ast_odbc_request_obj(odbc_database, 0);
3072         if (obj) {
3073                 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
3074                 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?",odbc_table);
3075                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3076                 if (!stmt) {
3077                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3078                         ast_odbc_release_obj(obj);
3079                         goto yuck;
3080                 }
3081                 res = SQLFetch(stmt);
3082                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3083                         ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3084                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3085                         ast_odbc_release_obj(obj);
3086                         goto yuck;
3087                 }
3088                 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3089                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3090                         ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3091                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3092                         ast_odbc_release_obj(obj);
3093                         goto yuck;
3094                 }
3095                 if (sscanf(rowdata, "%d", &x) != 1)
3096                         ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
3097                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3098                 ast_odbc_release_obj(obj);
3099         } else
3100                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3101 yuck:   
3102         return x;
3103 }
3104
3105 /*!
3106  * \brief returns the one-based count for messages.
3107  * \param vmu
3108  * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
3109  *
3110  * This method is used when mailboxes are stored in an ODBC back end.
3111  * The message index is zero-based, the first message will be index 0. For convenient display it is good to have the
3112  * one-based messages.
3113  * This method just calls last_message_index and returns +1 of its value.
3114  *
3115  * \return the value greater than zero on success to indicate the one-based count of messages, less than zero on error.
3116  */
3117 static int count_messages(struct ast_vm_user *vmu, char *dir)
3118 {
3119         return last_message_index(vmu, dir) + 1;
3120 }
3121
3122 /*!
3123  * \brief Deletes a message from the mailbox folder.
3124  * \param sdir The mailbox folder to work in.
3125  * \param smsg The message index to be deleted.
3126  *
3127  * This method is used when mailboxes are stored in an ODBC back end.
3128  * The specified message is directly deleted from the database 'voicemessages' table.
3129  * 
3130  * \return the value greater than zero on success to indicate the number of messages, less than zero on error.
3131  */
3132 static void delete_file(char *sdir, int smsg)
3133 {
3134         SQLHSTMT stmt;
3135         char sql[PATH_MAX];
3136         char msgnums[20];
3137         char *argv[] = { sdir, msgnums };
3138         struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3139
3140         struct odbc_obj *obj;
3141         obj = ast_odbc_request_obj(odbc_database, 0);
3142         if (obj) {
3143                 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3144                 snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?",odbc_table);
3145                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3146                 if (!stmt)
3147                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3148                 else
3149                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3150                 ast_odbc_release_obj(obj);
3151         } else
3152                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3153         return; 
3154 }
3155
3156 /*!
3157  * \brief Copies a voicemail from one mailbox to another.
3158  * \param sdir the folder for which to look for the message to be copied.
3159  * \param smsg the index of the message to be copied.
3160  * \param ddir the destination folder to copy the message into.
3161  * \param dmsg the index to be used for the copied message.
3162  * \param dmailboxuser The user who owns the mailbox tha contains the destination folder.
3163  * \param dmailboxcontext The context for the destination user.
3164  *
3165  * This method is used for the COPY macro when mailboxes are stored in an ODBC back end.
3166  */
3167 static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailboxuser, char *dmailboxcontext)
3168 {
3169         SQLHSTMT stmt;
3170         char sql[512];
3171         char msgnums[20];
3172         char msgnumd[20];
3173         struct odbc_obj *obj;
3174         char *argv[] = { ddir, msgnumd, dmailboxuser, dmailboxcontext, sdir, msgnums };
3175         struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
3176
3177         delete_file(ddir, dmsg);
3178         obj = ast_odbc_request_obj(odbc_database, 0);
3179         if (obj) {
3180                 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3181                 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
3182                 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir, msgnum, context, macrocontext, callerid, origtime, duration, recording, mailboxuser, mailboxcontext, flag) SELECT ?,?,context,macrocontext,callerid,origtime,duration,recording,flag,?,? FROM %s WHERE dir=? AND msgnum=?",odbc_table,odbc_table);
3183                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3184                 if (!stmt)
3185                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
3186                 else
3187                         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3188                 ast_odbc_release_obj(obj);
3189         } else
3190                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3191         return; 
3192 }
3193
3194 struct insert_data {
3195         char *sql;
3196         char *dir;
3197         char *msgnums;
3198         void *data;
3199         SQLLEN datalen;
3200         SQLLEN indlen;
3201         const char *context;
3202         const char *macrocontext;
3203         const char *callerid;
3204         const char *origtime;
3205         const char *duration;
3206         char *mailboxuser;
3207         char *mailboxcontext;
3208         const char *category;
3209         const char *flag;
3210 };
3211
3212 static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
3213 {
3214         struct insert_data *data = vdata;
3215         int res;
3216         SQLHSTMT stmt;
3217
3218         res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
3219         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3220                 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
3221                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3222                 return NULL;
3223         }
3224
3225         SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->dir), 0, (void *)data->dir, 0, NULL);
3226         SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->msgnums), 0, (void *)data->msgnums, 0, NULL);
3227         SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, data->datalen, 0, (void *)data->data, data->datalen, &data->indlen);
3228         SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->context), 0, (void *)data->context, 0, NULL);
3229         SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->macrocontext), 0, (void *)data->macrocontext, 0, NULL);
3230         SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->callerid), 0, (void *)data->callerid, 0, NULL);
3231         SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->origtime), 0, (void *)data->origtime, 0, NULL);
3232         SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->duration), 0, (void *)data->duration, 0, NULL);
3233         SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxuser), 0, (void *)data->mailboxuser, 0, NULL);
3234         SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxcontext), 0, (void *)data->mailboxcontext, 0, NULL);
3235         SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->flag), 0, (void *)data->flag, 0, NULL);
3236         if (!ast_strlen_zero(data->category)) {
3237                 SQLBindParameter(stmt, 12, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->category), 0, (void *)data->category, 0, NULL);
3238         }
3239         res = SQLExecDirect(stmt, (unsigned char *)data->sql, SQL_NTS);
3240         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3241                 ast_log(AST_LOG_WARNING, "SQL Direct Execute failed!\n");
3242                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3243                 return NULL;
3244         }
3245
3246         return stmt;
3247 }
3248
3249 /*!
3250  * \brief Stores a voicemail into the database.
3251  * \param dir the folder the mailbox folder to store the message.
3252  * \param mailboxuser the user owning the mailbox folder.
3253  * \param mailboxcontext
3254  * \param msgnum the message index for the message to be stored.
3255  *
3256  * This method is used when mailboxes are stored in an ODBC back end.
3257  * The message sound file and information file is looked up on the file system. 
3258  * A SQL query is invoked to store the message into the (MySQL) database.
3259  *
3260  * \return the zero on success -1 on error.
3261  */
3262 static int store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum)
3263 {
3264         int res = 0;
3265         int fd = -1;
3266         void *fdm = MAP_FAILED;
3267         size_t fdlen = -1;
3268         SQLHSTMT stmt;
3269         char sql[PATH_MAX];
3270         char msgnums[20];
3271         char fn[PATH_MAX];
3272         char full_fn[PATH_MAX];
3273         char fmt[80]="";
3274         char *c;
3275         struct ast_config *cfg=NULL;
3276         struct odbc_obj *obj;
3277         struct insert_data idata = { .sql = sql, .msgnums = msgnums, .dir = dir, .mailboxuser = mailboxuser, .mailboxcontext = mailboxcontext };
3278         struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
3279
3280         delete_file(dir, msgnum);
3281         if (!(obj = ast_odbc_request_obj(odbc_database, 0))) {
3282                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3283                 return -1;
3284         }
3285
3286         do {
3287                 ast_copy_string(fmt, vmfmts, sizeof(fmt));
3288                 c = strchr(fmt, '|');
3289                 if (c)
3290                         *c = '\0';
3291                 if (!strcasecmp(fmt, "wav49"))
3292                         strcpy(fmt, "WAV");
3293                 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
3294                 if (msgnum > -1)
3295                         make_file(fn, sizeof(fn), dir, msgnum);
3296                 else
3297                         ast_copy_string(fn, dir, sizeof(fn));
3298                 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
3299                 cfg = ast_config_load(full_fn, config_flags);
3300                 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
3301                 fd = open(full_fn, O_RDWR);
3302                 if (fd < 0) {
3303                         ast_log(AST_LOG_WARNING, "Open of sound file '%s' failed: %s\n", full_fn, strerror(errno));
3304                         res = -1;
3305                         break;
3306                 }
3307                 if (cfg && cfg != CONFIG_STATUS_FILEINVALID) {
3308                         if (!(idata.context = ast_variable_retrieve(cfg, "message", "context"))) {
3309                                 idata.context = "";
3310                         }
3311                         if (!(idata.macrocontext = ast_variable_retrieve(cfg, "message", "macrocontext"))) {
3312                                 idata.macrocontext = "";
3313                         }
3314                         if (!(idata.callerid = ast_variable_retrieve(cfg, "message", "callerid"))) {
3315                                 idata.callerid = "";
3316                         }
3317                         if (!(idata.origtime = ast_variable_retrieve(cfg, "message", "origtime"))) {
3318                                 idata.origtime = "";
3319                         }
3320                         if (!(idata.duration = ast_variable_retrieve(cfg, "message", "duration"))) {
3321                                 idata.duration = "";
3322                         }
3323                         if (!(idata.category = ast_variable_retrieve(cfg, "message", "category"))) {
3324                                 idata.category = "";
3325                         }
3326                         if (!(idata.flag = ast_variable_retrieve(cfg, "message", "flag"))) {
3327                                 idata.flag = "";
3328                         }
3329                 }
3330                 fdlen = lseek(fd, 0, SEEK_END);
3331                 lseek(fd, 0, SEEK_SET);
3332                 printf("Length is %zd\n", fdlen);
3333                 fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0);
3334                 if (fdm == MAP_FAILED) {
3335                         ast_log(AST_LOG_WARNING, "Memory map failed!\n");
3336                         res = -1;
3337                         break;
3338                 } 
3339                 idata.data = fdm;
3340                 idata.datalen = idata.indlen = fdlen;
3341
3342                 if (!ast_strlen_zero(idata.category)) 
3343                         snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,category) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",odbc_table); 
3344                 else
3345                         snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag) VALUES (?,?,?,?,?,?,?,?,?,?,?)",odbc_table);
3346
3347                 if ((stmt = ast_odbc_direct_execute(obj, insert_data_cb, &idata))) {
3348                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3349                 } else {
3350                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3351                         res = -1;
3352                 }
3353         } while (0);
3354         if (obj) {
3355                 ast_odbc_release_obj(obj);
3356         }
3357         if (cfg)
3358                 ast_config_destroy(cfg);
3359         if (fdm != MAP_FAILED)
3360                 munmap(fdm, fdlen);
3361         if (fd > -1)
3362                 close(fd);
3363         return res;
3364 }
3365
3366 /*!
3367  * \brief Renames a message in a mailbox folder.
3368  * \param sdir The folder of the message to be renamed.
3369  * \param smsg The index of the message to be renamed.
3370  * \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.
3371  * \param mailboxcontext The context to be set for the message. Usually this will be the same as the original context.
3372  * \param ddir The destination folder for the message to be renamed into
3373  * \param dmsg The destination message for the message to be renamed.
3374  *
3375  * This method is used by the RENAME macro when mailboxes are stored in an ODBC back end.
3376  * 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.
3377  * 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.
3378  */
3379 static void rename_file(char *sdir, int smsg, char *mailboxuser, char *mailboxcontext, char *ddir, int dmsg)
3380 {
3381         SQLHSTMT stmt;
3382         char sql[PATH_MAX];
3383         char msgnums[20];
3384         char msgnumd[20];
3385         struct odbc_obj *obj;
3386         char *argv[] = { ddir, msgnumd, mailboxuser, mailboxcontext, sdir, msgnums };
3387         struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
3388
3389         delete_file(ddir, dmsg);
3390         obj = ast_odbc_request_obj(odbc_database, 0);
3391         if (obj) {
3392                 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3393                 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
3394                 snprintf(sql, sizeof(sql), "UPDATE %s SET dir=?, msgnum=?, mailboxuser=?, mailboxcontext=? WHERE dir=? AND msgnum=?",odbc_table);
3395                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3396                 if (!stmt)
3397                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3398                 else
3399                         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3400                 ast_odbc_release_obj(obj);
3401         } else
3402                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3403         return; 
3404 }
3405
3406 /*!
3407  * \brief Removes a voicemail message file.
3408  * \param dir the path to the message file.
3409  * \param msgnum the unique number for the message within the mailbox.
3410  *
3411  * Removes the message content file and the information file.
3412  * This method is used by the DISPOSE macro when mailboxes are stored in an ODBC back end.
3413  * Typical use is to clean up after a RETRIEVE operation. 
3414  * Note that this does not remove the message from the mailbox folders, to do that we would use delete_file().
3415  * \return zero on success, -1 on error.
3416  */
3417 static int remove_file(char *dir, int msgnum)
3418 {
3419         char fn[PATH_MAX];
3420         char full_fn[PATH_MAX];
3421         char msgnums[80];
3422         
3423         if (msgnum > -1) {
3424                 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
3425                 make_file(fn, sizeof(fn), dir, msgnum);
3426         } else
3427                 ast_copy_string(fn, dir, sizeof(fn));
3428         ast_filedelete(fn, NULL);       
3429         snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
3430         unlink(full_fn);
3431         return 0;
3432 }
3433 #else
3434 #ifndef IMAP_STORAGE
3435 /*!
3436  * \brief Find all .txt files - even if they are not in sequence from 0000.
3437  * \param vmu
3438  * \param dir
3439  *
3440  * This method is used when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
3441  *
3442  * \return the count of messages, zero or more.
3443  */
3444 static int count_messages(struct ast_vm_user *vmu, char *dir)
3445 {
3446
3447         int vmcount = 0;
3448         DIR *vmdir = NULL;
3449         struct dirent *vment = NULL;
3450
3451         if (vm_lock_path(dir))
3452                 return ERROR_LOCK_PATH;
3453
3454         if ((vmdir = opendir(dir))) {
3455                 while ((vment = readdir(vmdir))) {
3456                         if (strlen(vment->d_name) > 7 && !strncmp(vment->d_name + 7, ".txt", 4)) {
3457                                 vmcount++;
3458                         }
3459                 }
3460                 closedir(vmdir);
3461         }
3462         ast_unlock_path(dir);
3463         
3464         return vmcount;
3465 }
3466
3467 /*!
3468  * \brief Renames a message in a mailbox folder.
3469  * \param sfn The path to the mailbox information and data file to be renamed.
3470  * \param dfn The path for where the message data and information files will be renamed to.
3471  *
3472  * This method is used by the RENAME macro when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
3473  */
3474 static void rename_file(char *sfn, char *dfn)
3475 {
3476         char stxt[PATH_MAX];
3477         char dtxt[PATH_MAX];
3478         ast_filerename(sfn,dfn,NULL);
3479         snprintf(stxt, sizeof(stxt), "%s.txt", sfn);
3480         snprintf(dtxt, sizeof(dtxt), "%s.txt", dfn);
3481         if (ast_check_realtime("voicemail_data")) {
3482                 ast_update_realtime("voicemail_data", "filename", sfn, "filename", dfn, SENTINEL);
3483         }
3484         rename(stxt, dtxt);
3485 }
3486
3487 /*! 
3488  * \brief Determines the highest message number in use for a given user and mailbox folder.
3489  * \param vmu 
3490  * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
3491  *
3492  * This method is used when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
3493  * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
3494  *
3495  * \note Should always be called with a lock already set on dir.
3496  * \return the value of zero or greaterto indicate the last message index in use, -1 to indicate none.
3497  */
3498 static int last_message_index(struct ast_vm_user *vmu, char *dir)
3499 {
3500         int x;
3501         unsigned char map[MAXMSGLIMIT] = "";
3502         DIR *msgdir;
3503         struct dirent *msgdirent;
3504         int msgdirint;
3505
3506         /* Reading the entire directory into a file map scales better than
3507          * doing a stat repeatedly on a predicted sequence.  I suspect this
3508          * is partially due to stat(2) internally doing a readdir(2) itself to
3509          * find each file. */
3510         msgdir = opendir(dir);
3511         while ((msgdirent = readdir(msgdir))) {
3512                 if (sscanf(msgdirent->d_name, "msg%d", &msgdirint) == 1 && msgdirint < MAXMSGLIMIT)
3513                         map[msgdirint] = 1;
3514         }
3515         closedir(msgdir);
3516
3517         for (x = 0; x < vmu->maxmsg; x++) {
3518                 if (map[x] == 0)
3519                         break;
3520         }
3521
3522         return x - 1;
3523 }
3524
3525 #endif /* #ifndef IMAP_STORAGE */
3526 #endif /* #else of #ifdef ODBC_STORAGE */
3527 #ifndef IMAP_STORAGE
3528 /*!
3529  * \brief Utility function to copy a file.
3530  * \param infile The path to the file to be copied. The file must be readable, it is opened in read only mode.
3531  * \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.
3532  *
3533  * 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.
3534  * The copy operation copies up to 4096 bytes at once.
3535  *
3536  * \return zero on success, -1 on error.
3537  */
3538 static int copy(char *infile, char *outfile)
3539 {
3540         int ifd;
3541         int ofd;
3542         int res;
3543         int len;
3544         char buf[4096];
3545
3546 #ifdef HARDLINK_WHEN_POSSIBLE
3547         /* Hard link if possible; saves disk space & is faster */
3548         if (link(infile, outfile)) {
3549 #endif
3550                 if ((ifd = open(infile, O_RDONLY)) < 0) {
3551                         ast_log(AST_LOG_WARNING, "Unable to open %s in read-only mode: %s\n", infile, strerror(errno));
3552                         return -1;
3553                 }
3554                 if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, VOICEMAIL_FILE_MODE)) < 0) {
3555                         ast_log(AST_LOG_WARNING, "Unable to open %s in write-only mode: %s\n", outfile, strerror(errno));
3556                         close(ifd);
3557                         return -1;
3558                 }
3559                 do {
3560                         len = read(ifd, buf, sizeof(buf));
3561                         if (len < 0) {
3562                                 ast_log(AST_LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
3563                                 close(ifd);
3564                                 close(ofd);
3565                                 unlink(outfile);
3566                         }
3567                         if (len) {
3568                                 res = write(ofd, buf, len);
3569                                 if (errno == ENOMEM || errno == ENOSPC || res != len) {
3570                                         ast_log(AST_LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno));
3571                                         close(ifd);
3572                                         close(ofd);
3573                                         unlink(outfile);
3574                                 }
3575                         }
3576                 } while (len);
3577                 close(ifd);
3578                 close(ofd);
3579                 return 0;
3580 #ifdef HARDLINK_WHEN_POSSIBLE
3581         } else {
3582