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