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