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