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