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