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