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