Flag field in wrong position.
[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
787 static struct ast_flags globalflags = {0};
788
789 static int saydurationminfo;
790
791 static char dialcontext[AST_MAX_CONTEXT] = "";
792 static char callcontext[AST_MAX_CONTEXT] = "";
793 static char exitcontext[AST_MAX_CONTEXT] = "";
794
795 static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
796
797
798 static char *emailbody = NULL;
799 static char *emailsubject = NULL;
800 static char *pagerbody = NULL;
801 static char *pagersubject = NULL;
802 static char fromstring[100];
803 static char pagerfromstring[100];
804 static char charset[32] = "ISO-8859-1";
805
806 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
807 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
808 static int adsiver = 1;
809 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
810
811 /* Forward declarations - generic */
812 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
813 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);
814 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
815 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
816                         char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
817                         signed char record_gain, struct vm_state *vms, char *flag);
818 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
819 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
820 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);
821 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);
822 static void apply_options(struct ast_vm_user *vmu, const char *options);
823 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);
824 static int is_valid_dtmf(const char *key);
825
826 #if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
827 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
828 #endif
829
830 static char *strip_control(const char *input, char *buf, size_t buflen)
831 {
832         char *bufptr = buf;
833         for (; *input; input++) {
834                 if (*input < 32) {
835                         continue;
836                 }
837                 *bufptr++ = *input;
838                 if (bufptr == buf + buflen - 1) {
839                         break;
840                 }
841         }
842         *bufptr = '\0';
843         return buf;
844 }
845
846
847 /*!
848  * \brief Sets default voicemail system options to a voicemail user.
849  *
850  * This applies select global settings to a newly created (dynamic) instance of a voicemail user.
851  * - all the globalflags
852  * - the saydurationminfo
853  * - the callcontext
854  * - the dialcontext
855  * - the exitcontext
856  * - vmmaxsecs, vmmaxmsg, maxdeletedmsg
857  * - volume gain.
858  */
859 static void populate_defaults(struct ast_vm_user *vmu)
860 {
861         ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);     
862         if (saydurationminfo)
863                 vmu->saydurationm = saydurationminfo;
864         ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
865         ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
866         ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
867         ast_copy_string(vmu->zonetag, zonetag, sizeof(vmu->zonetag));
868         if (vmmaxsecs)
869                 vmu->maxsecs = vmmaxsecs;
870         if (maxmsg)
871                 vmu->maxmsg = maxmsg;
872         if (maxdeletedmsg)
873                 vmu->maxdeletedmsg = maxdeletedmsg;
874         vmu->volgain = volgain;
875         vmu->emailsubject = NULL;
876         vmu->emailbody = NULL;
877 }
878
879 /*!
880  * \brief Sets a a specific property value.
881  * \param vmu The voicemail user object to work with.
882  * \param var The name of the property to be set.
883  * \param value The value to be set to the property.
884  * 
885  * The property name must be one of the understood properties. See the source for details.
886  */
887 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
888 {
889         int x;
890         if (!strcasecmp(var, "attach")) {
891                 ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
892         } else if (!strcasecmp(var, "attachfmt")) {
893                 ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
894         } else if (!strcasecmp(var, "serveremail")) {
895                 ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
896         } else if (!strcasecmp(var, "language")) {
897                 ast_copy_string(vmu->language, value, sizeof(vmu->language));
898         } else if (!strcasecmp(var, "tz")) {
899                 ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
900 #ifdef IMAP_STORAGE
901         } else if (!strcasecmp(var, "imapuser")) {
902                 ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
903         } else if (!strcasecmp(var, "imappassword") || !strcasecmp(var, "imapsecret")) {
904                 ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
905         } else if (!strcasecmp(var, "imapvmshareid")) {
906                 ast_copy_string(vmu->imapvmshareid, value, sizeof(vmu->imapvmshareid));
907 #endif
908         } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
909                 ast_set2_flag(vmu, ast_true(value), VM_DELETE); 
910         } else if (!strcasecmp(var, "saycid")){
911                 ast_set2_flag(vmu, ast_true(value), VM_SAYCID); 
912         } else if (!strcasecmp(var, "sendvoicemail")){
913                 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL); 
914         } else if (!strcasecmp(var, "review")){
915                 ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
916         } else if (!strcasecmp(var, "tempgreetwarn")){
917                 ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);  
918         } else if (!strcasecmp(var, "messagewrap")){
919                 ast_set2_flag(vmu, ast_true(value), VM_MESSAGEWRAP);    
920         } else if (!strcasecmp(var, "operator")) {
921                 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);       
922         } else if (!strcasecmp(var, "envelope")){
923                 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);       
924         } else if (!strcasecmp(var, "moveheard")){
925                 ast_set2_flag(vmu, ast_true(value), VM_MOVEHEARD);
926         } else if (!strcasecmp(var, "sayduration")){
927                 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);    
928         } else if (!strcasecmp(var, "saydurationm")){
929                 if (sscanf(value, "%d", &x) == 1) {
930                         vmu->saydurationm = x;
931                 } else {
932                         ast_log(AST_LOG_WARNING, "Invalid min duration for say duration\n");
933                 }
934         } else if (!strcasecmp(var, "forcename")){
935                 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);      
936         } else if (!strcasecmp(var, "forcegreetings")){
937                 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);     
938         } else if (!strcasecmp(var, "callback")) {
939                 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
940         } else if (!strcasecmp(var, "dialout")) {
941                 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
942         } else if (!strcasecmp(var, "exitcontext")) {
943                 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
944         } else if (!strcasecmp(var, "maxmessage") || !strcasecmp(var, "maxsecs")) {
945                 if (vmu->maxsecs <= 0) {
946                         ast_log(AST_LOG_WARNING, "Invalid max message length of %s. Using global value %d\n", value, vmmaxsecs);
947                         vmu->maxsecs = vmmaxsecs;
948                 } else {
949                         vmu->maxsecs = atoi(value);
950                 }
951                 if (!strcasecmp(var, "maxmessage"))
952                         ast_log(AST_LOG_WARNING, "Option 'maxmessage' has been deprecated in favor of 'maxsecs'.  Please make that change in your voicemail config.\n");
953         } else if (!strcasecmp(var, "maxmsg")) {
954                 vmu->maxmsg = atoi(value);
955                 if (vmu->maxmsg <= 0) {
956                         ast_log(AST_LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %d\n", value, MAXMSG);
957                         vmu->maxmsg = MAXMSG;
958                 } else if (vmu->maxmsg > MAXMSGLIMIT) {
959                         ast_log(AST_LOG_WARNING, "Maximum number of messages per folder is %d. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
960                         vmu->maxmsg = MAXMSGLIMIT;
961                 }
962         } else if (!strcasecmp(var, "backupdeleted")) {
963                 if (sscanf(value, "%d", &x) == 1)
964                         vmu->maxdeletedmsg = x;
965                 else if (ast_true(value))
966                         vmu->maxdeletedmsg = MAXMSG;
967                 else
968                         vmu->maxdeletedmsg = 0;
969
970                 if (vmu->maxdeletedmsg < 0) {
971                         ast_log(AST_LOG_WARNING, "Invalid number of deleted messages saved per mailbox backupdeleted=%s. Using default value %d\n", value, MAXMSG);
972                         vmu->maxdeletedmsg = MAXMSG;
973                 } else if (vmu->maxdeletedmsg > MAXMSGLIMIT) {
974                         ast_log(AST_LOG_WARNING, "Maximum number of deleted messages saved per mailbox is %d. Cannot accept value backupdeleted=%s\n", MAXMSGLIMIT, value);
975                         vmu->maxdeletedmsg = MAXMSGLIMIT;
976                 }
977         } else if (!strcasecmp(var, "volgain")) {
978                 sscanf(value, "%lf", &vmu->volgain);
979         } else if (!strcasecmp(var, "options")) {
980                 apply_options(vmu, value);
981         }
982 }
983
984 static char *vm_check_password_shell(char *command, char *buf, size_t len) 
985 {
986         int fds[2], pid = 0;
987
988         memset(buf, 0, len);
989
990         if (pipe(fds)) {
991                 snprintf(buf, len, "FAILURE: Pipe failed: %s", strerror(errno));
992         } else {
993                 /* good to go*/
994                 pid = ast_safe_fork(0);
995
996                 if (pid < 0) {
997                         /* ok maybe not */
998                         close(fds[0]);
999                         close(fds[1]);
1000                         snprintf(buf, len, "FAILURE: Fork failed");
1001                 } else if (pid) {
1002                         /* parent */
1003                         close(fds[1]);
1004                         if (read(fds[0], buf, len) < 0) {
1005                                 ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
1006                         }
1007                         close(fds[0]);
1008                 } else {
1009                         /*  child */
1010                         AST_DECLARE_APP_ARGS(arg,
1011                                 AST_APP_ARG(v)[20];
1012                         );
1013                         char *mycmd = ast_strdupa(command);
1014
1015                         close(fds[0]);
1016                         dup2(fds[1], STDOUT_FILENO);
1017                         close(fds[1]);
1018                         ast_close_fds_above_n(STDOUT_FILENO);
1019
1020                         AST_NONSTANDARD_APP_ARGS(arg, mycmd, ' ');
1021
1022                         execv(arg.v[0], arg.v); 
1023                         printf("FAILURE: %s", strerror(errno));
1024                         _exit(0);
1025                 }
1026         }
1027         return buf;
1028 }
1029
1030 /*!
1031  * \brief Check that password meets minimum required length
1032  * \param vmu The voicemail user to change the password for.
1033  * \param password The password string to check
1034  *
1035  * \return zero on ok, 1 on not ok.
1036  */
1037 static int check_password(struct ast_vm_user *vmu, char *password)
1038 {
1039         /* check minimum length */
1040         if (strlen(password) < minpassword)
1041                 return 1;
1042         if (!ast_strlen_zero(ext_pass_check_cmd)) {
1043                 char cmd[255], buf[255];
1044
1045                 ast_log(AST_LOG_DEBUG, "Verify password policies for %s\n", password);
1046
1047                 snprintf(cmd, sizeof(cmd), "%s %s %s %s %s", ext_pass_check_cmd, vmu->mailbox, vmu->context, vmu->password, password);
1048                 if (vm_check_password_shell(cmd, buf, sizeof(buf))) {
1049                         ast_debug(5, "Result: %s\n", buf);
1050                         if (!strncasecmp(buf, "VALID", 5)) {
1051                                 ast_debug(3, "Passed password check: '%s'\n", buf);
1052                                 return 0;
1053                         } else if (!strncasecmp(buf, "FAILURE", 7)) {
1054                                 ast_log(AST_LOG_WARNING, "Unable to execute password validation script: '%s'.\n", buf);
1055                                 return 0;
1056                         } else {
1057                                 ast_log(AST_LOG_NOTICE, "Password doesn't match policies for user %s %s\n", vmu->mailbox, password);
1058                                 return 1;
1059                         }
1060                 }
1061         }
1062         return 0;
1063 }
1064
1065 /*! 
1066  * \brief Performs a change of the voicemail passowrd in the realtime engine.
1067  * \param vmu The voicemail user to change the password for.
1068  * \param password The new value to be set to the password for this user.
1069  * 
1070  * This only works if the voicemail user has a unique id, and if there is a realtime engine configured.
1071  * This is called from the (top level) vm_change_password.
1072  *
1073  * \return zero on success, -1 on error.
1074  */
1075 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
1076 {
1077         int res;
1078         if (!ast_strlen_zero(vmu->uniqueid)) {
1079                 if (strlen(password) > 10) {
1080                         ast_realtime_require_field("voicemail", "password", RQ_CHAR, strlen(password), SENTINEL);
1081                 }
1082                 res = ast_update2_realtime("voicemail", "context", vmu->context, "mailbox", vmu->mailbox, SENTINEL, "password", password, SENTINEL);
1083                 if (res > 0) {
1084                         ast_copy_string(vmu->password, password, sizeof(vmu->password));
1085                         res = 0;
1086                 } else if (!res) {
1087                         res = -1;
1088                 }
1089                 return res;
1090         }
1091         return -1;
1092 }
1093
1094 /*!
1095  * \brief Destructively Parse options and apply.
1096  */
1097 static void apply_options(struct ast_vm_user *vmu, const char *options)
1098 {       
1099         char *stringp;
1100         char *s;
1101         char *var, *value;
1102         stringp = ast_strdupa(options);
1103         while ((s = strsep(&stringp, "|"))) {
1104                 value = s;
1105                 if ((var = strsep(&value, "=")) && value) {
1106                         apply_option(vmu, var, value);
1107                 }
1108         }       
1109 }
1110
1111 /*!
1112  * \brief Loads the options specific to a voicemail user.
1113  * 
1114  * This is called when a vm_user structure is being set up, such as from load_options.
1115  */
1116 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
1117 {
1118         for (; var; var = var->next) {
1119                 if (!strcasecmp(var->name, "vmsecret")) {
1120                         ast_copy_string(retval->password, var->value, sizeof(retval->password));
1121                 } else if (!strcasecmp(var->name, "secret") || !strcasecmp(var->name, "password")) { /* don't overwrite vmsecret if it exists */
1122                         if (ast_strlen_zero(retval->password))
1123                                 ast_copy_string(retval->password, var->value, sizeof(retval->password));
1124                 } else if (!strcasecmp(var->name, "uniqueid")) {
1125                         ast_copy_string(retval->uniqueid, var->value, sizeof(retval->uniqueid));
1126                 } else if (!strcasecmp(var->name, "pager")) {
1127                         ast_copy_string(retval->pager, var->value, sizeof(retval->pager));
1128                 } else if (!strcasecmp(var->name, "email")) {
1129                         ast_copy_string(retval->email, var->value, sizeof(retval->email));
1130                 } else if (!strcasecmp(var->name, "fullname")) {
1131                         ast_copy_string(retval->fullname, var->value, sizeof(retval->fullname));
1132                 } else if (!strcasecmp(var->name, "context")) {
1133                         ast_copy_string(retval->context, var->value, sizeof(retval->context));
1134                 } else if (!strcasecmp(var->name, "emailsubject")) {
1135                         retval->emailsubject = ast_strdup(var->value);
1136                 } else if (!strcasecmp(var->name, "emailbody")) {
1137                         retval->emailbody = ast_strdup(var->value);
1138 #ifdef IMAP_STORAGE
1139                 } else if (!strcasecmp(var->name, "imapuser")) {
1140                         ast_copy_string(retval->imapuser, var->value, sizeof(retval->imapuser));
1141                 } else if (!strcasecmp(var->name, "imappassword") || !strcasecmp(var->name, "imapsecret")) {
1142                         ast_copy_string(retval->imappassword, var->value, sizeof(retval->imappassword));
1143                 } else if (!strcasecmp(var->name, "imapvmshareid")) {
1144                         ast_copy_string(retval->imapvmshareid, var->value, sizeof(retval->imapvmshareid));
1145 #endif
1146                 } else
1147                         apply_option(retval, var->name, var->value);
1148         }
1149 }
1150
1151 /*!
1152  * \brief Determines if a DTMF key entered is valid.
1153  * \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.
1154  *
1155  * Tests the character entered against the set of valid DTMF characters. 
1156  * \return 1 if the character entered is a valid DTMF digit, 0 if the character is invalid.
1157  */
1158 static int is_valid_dtmf(const char *key)
1159 {
1160         int i;
1161         char *local_key = ast_strdupa(key);
1162
1163         for (i = 0; i < strlen(key); ++i) {
1164                 if (!strchr(VALID_DTMF, *local_key)) {
1165                         ast_log(AST_LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
1166                         return 0;
1167                 }
1168                 local_key++;
1169         }
1170         return 1;
1171 }
1172
1173 /*!
1174  * \brief Finds a voicemail user from the realtime engine.
1175  * \param ivm
1176  * \param context
1177  * \param mailbox
1178  *
1179  * 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.
1180  *
1181  * \return The ast_vm_user structure for the user that was found.
1182  */
1183 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1184 {
1185         struct ast_variable *var;
1186         struct ast_vm_user *retval;
1187
1188         if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
1189                 if (!ivm)
1190                         ast_set_flag(retval, VM_ALLOCED);       
1191                 else
1192                         memset(retval, 0, sizeof(*retval));
1193                 if (mailbox) 
1194                         ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
1195                 populate_defaults(retval);
1196                 if (!context && ast_test_flag((&globalflags), VM_SEARCH))
1197                         var = ast_load_realtime("voicemail", "mailbox", mailbox, SENTINEL);
1198                 else
1199                         var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, SENTINEL);
1200                 if (var) {
1201                         apply_options_full(retval, var);
1202                         ast_variables_destroy(var);
1203                 } else { 
1204                         if (!ivm) 
1205                                 ast_free(retval);
1206                         retval = NULL;
1207                 }       
1208         } 
1209         return retval;
1210 }
1211
1212 /*!
1213  * \brief Finds a voicemail user from the users file or the realtime engine.
1214  * \param ivm
1215  * \param context
1216  * \param mailbox
1217  * 
1218  * \return The ast_vm_user structure for the user that was found.
1219  */
1220 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1221 {
1222         /* This function could be made to generate one from a database, too */
1223         struct ast_vm_user *vmu = NULL, *cur;
1224         AST_LIST_LOCK(&users);
1225
1226         if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
1227                 context = "default";
1228
1229         AST_LIST_TRAVERSE(&users, cur, list) {
1230                 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
1231                         break;
1232                 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
1233                         break;
1234         }
1235         if (cur) {
1236                 /* Make a copy, so that on a reload, we have no race */
1237                 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
1238                         memcpy(vmu, cur, sizeof(*vmu));
1239                         ast_set2_flag(vmu, !ivm, VM_ALLOCED);
1240                         AST_LIST_NEXT(vmu, list) = NULL;
1241                 }
1242         } else
1243                 vmu = find_user_realtime(ivm, context, mailbox);
1244         AST_LIST_UNLOCK(&users);
1245         return vmu;
1246 }
1247
1248 /*!
1249  * \brief Resets a user password to a specified password.
1250  * \param context
1251  * \param mailbox
1252  * \param newpass
1253  *
1254  * This does the actual change password work, called by the vm_change_password() function.
1255  *
1256  * \return zero on success, -1 on error.
1257  */
1258 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
1259 {
1260         /* This function could be made to generate one from a database, too */
1261         struct ast_vm_user *cur;
1262         int res = -1;
1263         AST_LIST_LOCK(&users);
1264         AST_LIST_TRAVERSE(&users, cur, list) {
1265                 if ((!context || !strcasecmp(context, cur->context)) &&
1266                         (!strcasecmp(mailbox, cur->mailbox)))
1267                                 break;
1268         }
1269         if (cur) {
1270                 ast_copy_string(cur->password, newpass, sizeof(cur->password));
1271                 res = 0;
1272         }
1273         AST_LIST_UNLOCK(&users);
1274         return res;
1275 }
1276
1277 /*! 
1278  * \brief The handler for the change password option.
1279  * \param vmu The voicemail user to work with.
1280  * \param newpassword The new password (that has been gathered from the appropriate prompting).
1281  * 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.
1282  * It is also called when the user wants to change their password from menu option '5' on the mailbox options menu.
1283  */
1284 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
1285 {
1286         struct ast_config   *cfg = NULL;
1287         struct ast_variable *var = NULL;
1288         struct ast_category *cat = NULL;
1289         char *category = NULL, *value = NULL, *new = NULL;
1290         const char *tmp = NULL;
1291         struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
1292         if (!change_password_realtime(vmu, newpassword))
1293                 return;
1294
1295         /* check voicemail.conf */
1296         if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1297                 while ((category = ast_category_browse(cfg, category))) {
1298                         if (!strcasecmp(category, vmu->context)) {
1299                                 if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
1300                                         ast_log(AST_LOG_WARNING, "We could not find the mailbox.\n");
1301                                         break;
1302                                 }
1303                                 value = strstr(tmp, ",");
1304                                 if (!value) {
1305                                         ast_log(AST_LOG_WARNING, "variable has bad format.\n");
1306                                         break;
1307                                 }
1308                                 new = alloca((strlen(value)+strlen(newpassword)+1));
1309                                 sprintf(new, "%s%s", newpassword, value);
1310                                 if (!(cat = ast_category_get(cfg, category))) {
1311                                         ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
1312                                         break;
1313                                 }
1314                                 ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
1315                         }
1316                 }
1317                 /* save the results */
1318                 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1319                 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1320                 ast_config_text_file_save(VOICEMAIL_CONFIG, cfg, "AppVoicemail");
1321         }
1322         category = NULL;
1323         var = NULL;
1324         /* check users.conf and update the password stored for the mailbox*/
1325         /* if no vmsecret entry exists create one. */
1326         if ((cfg = ast_config_load("users.conf", config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1327                 ast_debug(4, "we are looking for %s\n", vmu->mailbox);
1328                 while ((category = ast_category_browse(cfg, category))) {
1329                         ast_debug(4, "users.conf: %s\n", category);
1330                         if (!strcasecmp(category, vmu->mailbox)) {
1331                                 if (!(tmp = ast_variable_retrieve(cfg, category, "vmsecret"))) {
1332                                         ast_debug(3, "looks like we need to make vmsecret!\n");
1333                                         var = ast_variable_new("vmsecret", newpassword, "");
1334                                 } 
1335                                 new = alloca(strlen(newpassword)+1);
1336                                 sprintf(new, "%s", newpassword);
1337                                 if (!(cat = ast_category_get(cfg, category))) {
1338                                         ast_debug(4, "failed to get category!\n");
1339                                         break;
1340                                 }
1341                                 if (!var)               
1342                                         ast_variable_update(cat, "vmsecret", new, NULL, 0);
1343                                 else
1344                                         ast_variable_append(cat, var);
1345                         }
1346                 }
1347                 /* save the results and clean things up */
1348                 reset_user_pw(vmu->context, vmu->mailbox, newpassword); 
1349                 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1350                 ast_config_text_file_save("users.conf", cfg, "AppVoicemail");
1351         }
1352 }
1353
1354 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
1355 {
1356         char buf[255];
1357         snprintf(buf, sizeof(buf), "%s %s %s %s", ext_pass_cmd, vmu->context, vmu->mailbox, newpassword);
1358         if (!ast_safe_system(buf)) {
1359                 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1360                 /* Reset the password in memory, too */
1361                 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1362         }
1363 }
1364
1365 /*! 
1366  * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1367  * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1368  * \param len The length of the path string that was written out.
1369  * 
1370  * The path is constructed as 
1371  *      VM_SPOOL_DIRcontext/ext/folder
1372  *
1373  * \return zero on success, -1 on error.
1374  */
1375 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
1376 {
1377         return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
1378 }
1379
1380 /*! 
1381  * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1382  * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1383  * \param len The length of the path string that was written out.
1384  * 
1385  * The path is constructed as 
1386  *      VM_SPOOL_DIRcontext/ext/folder
1387  *
1388  * \return zero on success, -1 on error.
1389  */
1390 static int make_file(char *dest, const int len, const char *dir, const int num)
1391 {
1392         return snprintf(dest, len, "%s/msg%04d", dir, num);
1393 }
1394
1395 /* same as mkstemp, but return a FILE * */
1396 static FILE *vm_mkftemp(char *template)
1397 {
1398         FILE *p = NULL;
1399         int pfd = mkstemp(template);
1400         chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
1401         if (pfd > -1) {
1402                 p = fdopen(pfd, "w+");
1403                 if (!p) {
1404                         close(pfd);
1405                         pfd = -1;
1406                 }
1407         }
1408         return p;
1409 }
1410
1411 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
1412  * \param dest    String. base directory.
1413  * \param len     Length of dest.
1414  * \param context String. Ignored if is null or empty string.
1415  * \param ext     String. Ignored if is null or empty string.
1416  * \param folder  String. Ignored if is null or empty string. 
1417  * \return -1 on failure, 0 on success.
1418  */
1419 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
1420 {
1421         mode_t  mode = VOICEMAIL_DIR_MODE;
1422         int res;
1423
1424         make_dir(dest, len, context, ext, folder);
1425         if ((res = ast_mkdir(dest, mode))) {
1426                 ast_log(AST_LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1427                 return -1;
1428         }
1429         return 0;
1430 }
1431
1432 static const char *mbox(int id)
1433 {
1434         static const char * const msgs[] = {
1435 #ifdef IMAP_STORAGE
1436                 imapfolder,
1437 #else
1438                 "INBOX",
1439 #endif
1440                 "Old",
1441                 "Work",
1442                 "Family",
1443                 "Friends",
1444                 "Cust1",
1445                 "Cust2",
1446                 "Cust3",
1447                 "Cust4",
1448                 "Cust5",
1449                 "Deleted",
1450                 "Urgent"
1451         };
1452         return (id >= 0 && id < ARRAY_LEN(msgs)) ? msgs[id] : "Unknown";
1453 }
1454
1455 static void free_user(struct ast_vm_user *vmu)
1456 {
1457         if (ast_test_flag(vmu, VM_ALLOCED)) {
1458                 if (vmu->emailbody != NULL) {
1459                         ast_free(vmu->emailbody);
1460                         vmu->emailbody = NULL;
1461                 }
1462                 if (vmu->emailsubject != NULL) {
1463                         ast_free(vmu->emailsubject);
1464                         vmu->emailsubject = NULL;
1465                 }
1466                 ast_free(vmu);
1467         }
1468 }
1469
1470 /* All IMAP-specific functions should go in this block. This
1471  * keeps them from being spread out all over the code */
1472 #ifdef IMAP_STORAGE
1473 static void vm_imap_delete(int msgnum, struct ast_vm_user *vmu)
1474 {
1475         char arg[10];
1476         struct vm_state *vms;
1477         unsigned long messageNum;
1478
1479         /* Greetings aren't stored in IMAP, so we can't delete them there */
1480         if (msgnum < 0) {
1481                 return;
1482         }
1483
1484         if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1485                 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);
1486                 return;
1487         }
1488
1489         /* find real message number based on msgnum */
1490         /* this may be an index into vms->msgArray based on the msgnum. */
1491         messageNum = vms->msgArray[msgnum];
1492         if (messageNum == 0) {
1493                 ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n", msgnum, messageNum);
1494                 return;
1495         }
1496         if (option_debug > 2)
1497                 ast_log(LOG_DEBUG, "deleting msgnum %d, which is mailbox message %lu\n", msgnum, messageNum);
1498         /* delete message */
1499         snprintf (arg, sizeof(arg), "%lu", messageNum);
1500         ast_mutex_lock(&vms->lock);
1501         mail_setflag (vms->mailstream, arg, "\\DELETED");
1502         ast_mutex_unlock(&vms->lock);
1503 }
1504
1505 static int imap_retrieve_greeting (const char *dir, const int msgnum, struct ast_vm_user *vmu)
1506 {
1507         struct vm_state *vms_p;
1508         char *file, *filename;
1509         char *attachment;
1510         int ret = 0, i;
1511         BODY *body;
1512
1513         /* This function is only used for retrieval of IMAP greetings
1514          * regular messages are not retrieved this way, nor are greetings
1515          * if they are stored locally*/
1516         if (msgnum > -1 || !imapgreetings) {
1517                 return 0;
1518         } else {
1519                 file = strrchr(ast_strdupa(dir), '/');
1520                 if (file)
1521                         *file++ = '\0';
1522                 else {
1523                         ast_debug (1, "Failed to procure file name from directory passed.\n");
1524                         return -1;
1525                 }
1526         }
1527
1528         /* check if someone is accessing this box right now... */
1529         if (!(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) ||!(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1530                 ast_log(AST_LOG_ERROR, "Voicemail state not found!\n");
1531                 return -1;
1532         }
1533         
1534         /* Greetings will never have a prepended message */
1535         *vms_p->introfn = '\0';
1536
1537         ast_mutex_lock(&vms_p->lock);
1538         ret = init_mailstream(vms_p, GREETINGS_FOLDER);
1539         if (!vms_p->mailstream) {
1540                 ast_log(AST_LOG_ERROR, "IMAP mailstream is NULL\n");
1541                 ast_mutex_unlock(&vms_p->lock);
1542                 return -1;
1543         }
1544
1545         /*XXX Yuck, this could probably be done a lot better */
1546         for (i = 0; i < vms_p->mailstream->nmsgs; i++) {
1547                 mail_fetchstructure(vms_p->mailstream, i + 1, &body);
1548                 /* We have the body, now we extract the file name of the first attachment. */
1549                 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1550                         attachment = ast_strdupa(body->nested.part->next->body.parameter->value);
1551                 } else {
1552                         ast_log(AST_LOG_ERROR, "There is no file attached to this IMAP message.\n");
1553                         ast_mutex_unlock(&vms_p->lock);
1554                         return -1;
1555                 }
1556                 filename = strsep(&attachment, ".");
1557                 if (!strcmp(filename, file)) {
1558                         ast_copy_string(vms_p->fn, dir, sizeof(vms_p->fn));
1559                         vms_p->msgArray[vms_p->curmsg] = i + 1;
1560                         save_body(body, vms_p, "2", attachment, 0);
1561                         ast_mutex_unlock(&vms_p->lock);
1562                         return 0;
1563                 }
1564         }
1565         ast_mutex_unlock(&vms_p->lock);
1566
1567         return -1;
1568 }
1569
1570 static int imap_retrieve_file(const char *dir, const int msgnum, const char *mailbox, const char *context)
1571 {
1572         BODY *body;
1573         char *header_content;
1574         char *attachedfilefmt;
1575         char buf[80];
1576         struct vm_state *vms;
1577         char text_file[PATH_MAX];
1578         FILE *text_file_ptr;
1579         int res = 0;
1580         struct ast_vm_user *vmu;
1581
1582         if (!(vmu = find_user(NULL, context, mailbox))) {
1583                 ast_log(LOG_WARNING, "Couldn't find user with mailbox %s@%s\n", mailbox, context);
1584                 return -1;
1585         }
1586         
1587         if (msgnum < 0) {
1588                 if (imapgreetings) {
1589                         res = imap_retrieve_greeting(dir, msgnum, vmu);
1590                         goto exit;
1591                 } else {
1592                         res = 0;
1593                         goto exit;
1594                 }
1595         }
1596
1597         /* Before anything can happen, we need a vm_state so that we can
1598          * actually access the imap server through the vms->mailstream
1599          */
1600         if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1601                 /* This should not happen. If it does, then I guess we'd
1602                  * need to create the vm_state, extract which mailbox to
1603                  * open, and then set up the msgArray so that the correct
1604                  * IMAP message could be accessed. If I have seen correctly
1605                  * though, the vms should be obtainable from the vmstates list
1606                  * and should have its msgArray properly set up.
1607                  */
1608                 ast_log(LOG_ERROR, "Couldn't find a vm_state for mailbox %s!!! Oh no!\n", vmu->mailbox);
1609                 res = -1;
1610                 goto exit;
1611         }
1612         
1613         make_file(vms->fn, sizeof(vms->fn), dir, msgnum);
1614         snprintf(vms->introfn, sizeof(vms->introfn), "%sintro", vms->fn);
1615
1616         /* Don't try to retrieve a message from IMAP if it already is on the file system */
1617         if (ast_fileexists(vms->fn, NULL, NULL) > 0) {
1618                 res = 0;
1619                 goto exit;
1620         }
1621
1622         if (option_debug > 2)
1623                 ast_log(LOG_DEBUG, "Before mail_fetchheaders, curmsg is: %d, imap messages is %lu\n", msgnum, vms->msgArray[msgnum]);
1624         if (vms->msgArray[msgnum] == 0) {
1625                 ast_log(LOG_WARNING, "Trying to access unknown message\n");
1626                 res = -1;
1627                 goto exit;
1628         }
1629
1630         /* This will only work for new messages... */
1631         ast_mutex_lock(&vms->lock);
1632         header_content = mail_fetchheader (vms->mailstream, vms->msgArray[msgnum]);
1633         ast_mutex_unlock(&vms->lock);
1634         /* empty string means no valid header */
1635         if (ast_strlen_zero(header_content)) {
1636                 ast_log(LOG_ERROR, "Could not fetch header for message number %ld\n", vms->msgArray[msgnum]);
1637                 res = -1;
1638                 goto exit;
1639         }
1640
1641         ast_mutex_lock(&vms->lock);
1642         mail_fetchstructure(vms->mailstream, vms->msgArray[msgnum], &body);
1643         ast_mutex_unlock(&vms->lock);
1644
1645         /* We have the body, now we extract the file name of the first attachment. */
1646         if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1647                 attachedfilefmt = ast_strdupa(body->nested.part->next->body.parameter->value);
1648         } else {
1649                 ast_log(LOG_ERROR, "There is no file attached to this IMAP message.\n");
1650                 res = -1;
1651                 goto exit;
1652         }
1653         
1654         /* Find the format of the attached file */
1655
1656         strsep(&attachedfilefmt, ".");
1657         if (!attachedfilefmt) {
1658                 ast_log(LOG_ERROR, "File format could not be obtained from IMAP message attachment\n");
1659                 res = -1;
1660                 goto exit;
1661         }
1662         
1663         save_body(body, vms, "2", attachedfilefmt, 0);
1664         if (save_body(body, vms, "3", attachedfilefmt, 1)) {
1665                 *vms->introfn = '\0';
1666         }
1667
1668         /* Get info from headers!! */
1669         snprintf(text_file, sizeof(text_file), "%s.%s", vms->fn, "txt");
1670
1671         if (!(text_file_ptr = fopen(text_file, "w"))) {
1672                 ast_log(LOG_WARNING, "Unable to open/create file %s: %s\n", text_file, strerror(errno));
1673         }
1674
1675         fprintf(text_file_ptr, "%s\n", "[message]");
1676
1677         get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf));
1678         fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, ""));
1679         get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf));
1680         fprintf(text_file_ptr, "<%s>\n", S_OR(buf, ""));
1681         get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf));
1682         fprintf(text_file_ptr, "context=%s\n", S_OR(buf, ""));
1683         get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf));
1684         fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, ""));
1685         get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf));
1686         fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, ""));
1687         get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf));
1688         fprintf(text_file_ptr, "category=%s\n", S_OR(buf, ""));
1689         get_header_by_tag(header_content, "X-Asterisk-VM-Flag:", buf, sizeof(buf));
1690         fprintf(text_file_ptr, "flag=%s\n", S_OR(buf, ""));
1691         fclose(text_file_ptr);
1692
1693 exit:
1694         free_user(vmu);
1695         return res;
1696 }
1697
1698 static int folder_int(const char *folder)
1699 {
1700         /*assume a NULL folder means INBOX*/
1701         if (!folder)
1702                 return 0;
1703         if (!strcasecmp(folder, imapfolder))
1704                 return 0;
1705         else if (!strcasecmp(folder, "Old"))
1706                 return 1;
1707         else if (!strcasecmp(folder, "Work"))
1708                 return 2;
1709         else if (!strcasecmp(folder, "Family"))
1710                 return 3;
1711         else if (!strcasecmp(folder, "Friends"))
1712                 return 4;
1713         else if (!strcasecmp(folder, "Cust1"))
1714                 return 5;
1715         else if (!strcasecmp(folder, "Cust2"))
1716                 return 6;
1717         else if (!strcasecmp(folder, "Cust3"))
1718                 return 7;
1719         else if (!strcasecmp(folder, "Cust4"))
1720                 return 8;
1721         else if (!strcasecmp(folder, "Cust5"))
1722                 return 9;
1723         else /*assume they meant INBOX if folder is not found otherwise*/
1724                 return 0;
1725 }
1726
1727 /*!
1728  * \brief Gets the number of messages that exist in a mailbox folder.
1729  * \param context
1730  * \param mailbox
1731  * \param folder
1732  * 
1733  * This method is used when IMAP backend is used.
1734  * \return The number of messages in this mailbox folder (zero or more).
1735  */
1736 static int messagecount(const char *context, const char *mailbox, const char *folder)
1737 {
1738         SEARCHPGM *pgm;
1739         SEARCHHEADER *hdr;
1740
1741         struct ast_vm_user *vmu, vmus;
1742         struct vm_state *vms_p;
1743         int ret = 0;
1744         int fold = folder_int(folder);
1745         int urgent = 0;
1746         
1747         /* If URGENT, then look at INBOX */
1748         if (fold == 11) {
1749                 fold = NEW_FOLDER;
1750                 urgent = 1;
1751         }
1752
1753         if (ast_strlen_zero(mailbox))
1754                 return 0;
1755
1756         /* We have to get the user before we can open the stream! */
1757         vmu = find_user(&vmus, context, mailbox);
1758         if (!vmu) {
1759                 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailbox, context);
1760                 return -1;
1761         } else {
1762                 /* No IMAP account available */
1763                 if (vmu->imapuser[0] == '\0') {
1764                         ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1765                         return -1;
1766                 }
1767         }
1768         
1769         /* No IMAP account available */
1770         if (vmu->imapuser[0] == '\0') {
1771                 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1772                 free_user(vmu);
1773                 return -1;
1774         }
1775
1776         /* check if someone is accessing this box right now... */
1777         vms_p = get_vm_state_by_imapuser(vmu->imapuser, 1);
1778         if (!vms_p) {
1779                 vms_p = get_vm_state_by_mailbox(mailbox, context, 1);
1780         }
1781         if (vms_p) {
1782                 ast_debug(3, "Returning before search - user is logged in\n");
1783                 if (fold == 0) { /* INBOX */
1784                         return vms_p->newmessages;
1785                 }
1786                 if (fold == 1) { /* Old messages */
1787                         return vms_p->oldmessages;
1788                 }
1789                 if (fold == 11) {/*Urgent messages*/
1790                         return vms_p->urgentmessages;
1791                 }
1792         }
1793
1794         /* add one if not there... */
1795         vms_p = get_vm_state_by_imapuser(vmu->imapuser, 0);
1796         if (!vms_p) {
1797                 vms_p = get_vm_state_by_mailbox(mailbox, context, 0);
1798         }
1799
1800         if (!vms_p) {
1801                 vms_p = create_vm_state_from_user(vmu);
1802         }
1803         ret = init_mailstream(vms_p, fold);
1804         if (!vms_p->mailstream) {
1805                 ast_log(AST_LOG_ERROR, "Houston we have a problem - IMAP mailstream is NULL\n");
1806                 return -1;
1807         }
1808         if (ret == 0) {
1809                 ast_mutex_lock(&vms_p->lock);
1810                 pgm = mail_newsearchpgm ();
1811                 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)(!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : mailbox));
1812                 hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", (char *) S_OR(context, "default"));
1813                 pgm->header = hdr;
1814                 if (fold != 1) {
1815                         pgm->unseen = 1;
1816                         pgm->seen = 0;
1817                 }
1818                 /* In the special case where fold is 1 (old messages) we have to do things a bit
1819                  * differently. Old messages are stored in the INBOX but are marked as "seen"
1820                  */
1821                 else {
1822                         pgm->unseen = 0;
1823                         pgm->seen = 1;
1824                 }
1825                 /* look for urgent messages */
1826                 if (urgent) {
1827                         pgm->flagged = 1;
1828                         pgm->unflagged = 0;
1829                 }
1830                 pgm->undeleted = 1;
1831                 pgm->deleted = 0;
1832
1833                 vms_p->vmArrayIndex = 0;
1834                 mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
1835                 if (fold == 0 && urgent == 0)
1836                         vms_p->newmessages = vms_p->vmArrayIndex;
1837                 if (fold == 1)
1838                         vms_p->oldmessages = vms_p->vmArrayIndex;
1839                 if (fold == 0 && urgent == 1)
1840                         vms_p->urgentmessages = vms_p->vmArrayIndex;
1841                 /*Freeing the searchpgm also frees the searchhdr*/
1842                 mail_free_searchpgm(&pgm);
1843                 ast_mutex_unlock(&vms_p->lock);
1844                 vms_p->updated = 0;
1845                 return vms_p->vmArrayIndex;
1846         } else {
1847                 ast_mutex_lock(&vms_p->lock);
1848                 mail_ping(vms_p->mailstream);
1849                 ast_mutex_unlock(&vms_p->lock);
1850         }
1851         return 0;
1852 }
1853
1854 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)
1855 {
1856         char *myserveremail = serveremail;
1857         char fn[PATH_MAX];
1858         char introfn[PATH_MAX];
1859         char mailbox[256];
1860         char *stringp;
1861         FILE *p = NULL;
1862         char tmp[80] = "/tmp/astmail-XXXXXX";
1863         long len;
1864         void *buf;
1865         int tempcopy = 0;
1866         STRING str;
1867         int ret; /* for better error checking */
1868         char *imap_flags = NIL;
1869
1870         /* Set urgent flag for IMAP message */
1871         if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) {
1872                 ast_debug(3, "Setting message flag \\\\FLAGGED.\n");
1873                 imap_flags = "\\FLAGGED";
1874         }
1875         
1876         /* Attach only the first format */
1877         fmt = ast_strdupa(fmt);
1878         stringp = fmt;
1879         strsep(&stringp, "|");
1880
1881         if (!ast_strlen_zero(vmu->serveremail))
1882                 myserveremail = vmu->serveremail;
1883
1884         if (msgnum > -1)
1885                 make_file(fn, sizeof(fn), dir, msgnum);
1886         else
1887                 ast_copy_string (fn, dir, sizeof(fn));
1888
1889         snprintf(introfn, sizeof(introfn), "%sintro", fn);
1890         if (ast_fileexists(introfn, NULL, NULL) <= 0) {
1891                 *introfn = '\0';
1892         }
1893         
1894         if (ast_strlen_zero(vmu->email)) {
1895                 /* We need the vmu->email to be set when we call make_email_file, but
1896                  * if we keep it set, a duplicate e-mail will be created. So at the end
1897                  * of this function, we will revert back to an empty string if tempcopy
1898                  * is 1.
1899                  */
1900                 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
1901                 tempcopy = 1;
1902         }
1903
1904         if (!strcmp(fmt, "wav49"))
1905                 fmt = "WAV";
1906         ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
1907
1908         /* Make a temporary file instead of piping directly to sendmail, in case the mail
1909            command hangs. */
1910         if (!(p = vm_mkftemp(tmp))) {
1911                 ast_log(AST_LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
1912                 if (tempcopy)
1913                         *(vmu->email) = '\0';
1914                 return -1;
1915         }
1916
1917         if (msgnum < 0 && imapgreetings) {
1918                 if ((ret = init_mailstream(vms, GREETINGS_FOLDER))) {
1919                         ast_log(AST_LOG_WARNING, "Unable to open mailstream.\n");
1920                         return -1;
1921                 }
1922                 imap_delete_old_greeting(fn, vms);
1923         }
1924
1925         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);
1926         /* read mail file to memory */
1927         len = ftell(p);
1928         rewind(p);
1929         if (!(buf = ast_malloc(len + 1))) {
1930                 ast_log(AST_LOG_ERROR, "Can't allocate %ld bytes to read message\n", len + 1);
1931                 fclose(p);
1932                 if (tempcopy)
1933                         *(vmu->email) = '\0';
1934                 return -1;
1935         }
1936         if (fread(buf, len, 1, p) < len) {
1937                 if (ferror(p)) {
1938                         ast_log(LOG_ERROR, "Short read while reading in mail file.\n");
1939                         return -1;
1940                 }
1941         }
1942         ((char *) buf)[len] = '\0';
1943         INIT(&str, mail_string, buf, len);
1944         ret = init_mailstream(vms, NEW_FOLDER);
1945         if (ret == 0) {
1946                 imap_mailbox_name(mailbox, sizeof(mailbox), vms, NEW_FOLDER, 1);
1947                 ast_mutex_lock(&vms->lock);
1948                 if(!mail_append_full(vms->mailstream, mailbox, imap_flags, NIL, &str))
1949                         ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
1950                 ast_mutex_unlock(&vms->lock);
1951                 fclose(p);
1952                 unlink(tmp);
1953                 ast_free(buf);
1954         } else {
1955                 ast_log(LOG_ERROR, "Could not initialize mailstream for %s\n", mailbox);
1956                 fclose(p);
1957                 unlink(tmp);
1958                 ast_free(buf);
1959                 return -1;
1960         }
1961         ast_debug(3, "%s stored\n", fn);
1962         
1963         if (tempcopy)
1964                 *(vmu->email) = '\0';
1965         
1966         return 0;
1967
1968 }
1969
1970 /*!
1971  * \brief Gets the number of messages that exist in the inbox folder.
1972  * \param mailbox_context
1973  * \param newmsgs The variable that is updated with the count of new messages within this inbox.
1974  * \param oldmsgs The variable that is updated with the count of old messages within this inbox.
1975  * \param urgentmsgs The variable that is updated with the count of urgent messages within this inbox.
1976  * 
1977  * This method is used when IMAP backend is used.
1978  * Simultaneously determines the count of new,old, and urgent messages. The total messages would then be the sum of these three.
1979  *
1980  * \return zero on success, -1 on error.
1981  */
1982
1983 static int inboxcount2(const char *mailbox_context, int *urgentmsgs, int *newmsgs, int *oldmsgs)
1984 {
1985         char tmp[PATH_MAX] = "";
1986         char *mailboxnc;
1987         char *context;
1988         char *mb;
1989         char *cur;
1990         if (newmsgs)
1991                 *newmsgs = 0;
1992         if (oldmsgs)
1993                 *oldmsgs = 0;
1994         if (urgentmsgs)
1995                 *urgentmsgs = 0;
1996
1997         ast_debug(3, "Mailbox is set to %s\n", mailbox_context);
1998         /* If no mailbox, return immediately */
1999         if (ast_strlen_zero(mailbox_context))
2000                 return 0;
2001         
2002         ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2003         context = strchr(tmp, '@');
2004         if (strchr(mailbox_context, ',')) {
2005                 int tmpnew, tmpold, tmpurgent;
2006                 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2007                 mb = tmp;
2008                 while ((cur = strsep(&mb, ", "))) {
2009                         if (!ast_strlen_zero(cur)) {
2010                                 if (inboxcount2(cur, urgentmsgs ? &tmpurgent : NULL, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2011                                         return -1;
2012                                 else {
2013                                         if (newmsgs)
2014                                                 *newmsgs += tmpnew; 
2015                                         if (oldmsgs)
2016                                                 *oldmsgs += tmpold;
2017                                         if (urgentmsgs)
2018                                                 *urgentmsgs += tmpurgent;
2019                                 }
2020                         }
2021                 }
2022                 return 0;
2023         }
2024         if (context) {
2025                 *context = '\0';
2026                 mailboxnc = tmp;
2027                 context++;
2028         } else {
2029                 context = "default";
2030                 mailboxnc = (char *) mailbox_context;
2031         }
2032         if (newmsgs) {
2033                 if ((*newmsgs = messagecount(context, mailboxnc, imapfolder)) < 0)
2034                         return -1;
2035         }
2036         if (oldmsgs) {
2037                 if ((*oldmsgs = messagecount(context, mailboxnc, "Old")) < 0)
2038                         return -1;
2039         }
2040         if (urgentmsgs) {
2041                 if((*urgentmsgs = messagecount(context, mailboxnc, "Urgent")) < 0)
2042                         return -1;
2043         }
2044         return 0;
2045 }
2046
2047 static int inboxcount(const char *mailbox_context, int *newmsgs, int *oldmsgs)
2048 {
2049         return inboxcount2(mailbox_context, NULL, newmsgs, oldmsgs);
2050 }
2051
2052 /** 
2053  * \brief Determines if the given folder has messages.
2054  * \param mailbox The @ delimited string for user@context. If no context is found, uses 'default' for the context.
2055  * \param folder the folder to look in
2056  *
2057  * This function is used when the mailbox is stored in an IMAP back end.
2058  * This invokes the messagecount(). Here we are interested in the presence of messages (> 0) only, not the actual count.
2059  * \return 1 if the folder has one or more messages. zero otherwise.
2060  */
2061
2062 static int has_voicemail(const char *mailbox, const char *folder)
2063 {
2064         char tmp[256], *tmp2, *box, *context;
2065         ast_copy_string(tmp, mailbox, sizeof(tmp));
2066         tmp2 = tmp;
2067         if (strchr(tmp2, ',')) {
2068                 while ((box = strsep(&tmp2, ","))) {
2069                         if (!ast_strlen_zero(box)) {
2070                                 if (has_voicemail(box, folder))
2071                                         return 1;
2072                         }
2073                 }
2074         }
2075         if ((context = strchr(tmp, '@')))
2076                 *context++ = '\0';
2077         else
2078                 context = "default";
2079         return messagecount(context, tmp, folder) ? 1 : 0;
2080 }
2081
2082 /*!
2083  * \brief Copies a message from one mailbox to another.
2084  * \param chan
2085  * \param vmu
2086  * \param imbox
2087  * \param msgnum
2088  * \param duration
2089  * \param recip
2090  * \param fmt
2091  * \param dir
2092  *
2093  * This works with IMAP storage based mailboxes.
2094  *
2095  * \return zero on success, -1 on error.
2096  */
2097 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)
2098 {
2099         struct vm_state *sendvms = NULL, *destvms = NULL;
2100         char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
2101         if (msgnum >= recip->maxmsg) {
2102                 ast_log(LOG_WARNING, "Unable to copy mail, mailbox %s is full\n", recip->mailbox);
2103                 return -1;
2104         }
2105         if (!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 0))) {
2106                 ast_log(LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
2107                 return -1;
2108         }
2109         if (!(destvms = get_vm_state_by_imapuser(recip->imapuser, 0))) {
2110                 ast_log(LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
2111                 return -1;
2112         }
2113         snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
2114         ast_mutex_lock(&sendvms->lock);
2115         if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(imbox)) == T)) {
2116                 ast_mutex_unlock(&sendvms->lock);
2117                 return 0;
2118         }
2119         ast_mutex_unlock(&sendvms->lock);
2120         ast_log(LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
2121         return -1;
2122 }
2123
2124 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int use_folder)
2125 {
2126         char tmp[256], *t = tmp;
2127         size_t left = sizeof(tmp);
2128         
2129         if (box == OLD_FOLDER) {
2130                 ast_copy_string(vms->curbox, mbox(NEW_FOLDER), sizeof(vms->curbox));
2131         } else {
2132                 ast_copy_string(vms->curbox, mbox(box), sizeof(vms->curbox));
2133         }
2134
2135         if (box == NEW_FOLDER) {
2136                 ast_copy_string(vms->vmbox, "vm-INBOX", sizeof(vms->vmbox));
2137         } else {
2138                 snprintf(vms->vmbox, sizeof(vms->vmbox), "vm-%s", mbox(box));
2139         }
2140
2141         /* Build up server information */
2142         ast_build_string(&t, &left, "{%s:%s/imap", imapserver, imapport);
2143
2144         /* Add authentication user if present */
2145         if (!ast_strlen_zero(authuser))
2146                 ast_build_string(&t, &left, "/authuser=%s", authuser);
2147
2148         /* Add flags if present */
2149         if (!ast_strlen_zero(imapflags))
2150                 ast_build_string(&t, &left, "/%s", imapflags);
2151
2152         /* End with username */
2153         ast_build_string(&t, &left, "/user=%s}", vms->imapuser);
2154         if (box == NEW_FOLDER || box == OLD_FOLDER)
2155                 snprintf(spec, len, "%s%s", tmp, use_folder? imapfolder: "INBOX");
2156         else if (box == GREETINGS_FOLDER)
2157                 snprintf(spec, len, "%s%s", tmp, greetingfolder);
2158         else {  /* Other folders such as Friends, Family, etc... */
2159                 if (!ast_strlen_zero(imapparentfolder)) {
2160                         /* imapparentfolder would typically be set to INBOX */
2161                         snprintf(spec, len, "%s%s%c%s", tmp, imapparentfolder, delimiter, mbox(box));
2162                 } else {
2163                         snprintf(spec, len, "%s%s", tmp, mbox(box));
2164                 }
2165         }
2166 }
2167
2168 static int init_mailstream(struct vm_state *vms, int box)
2169 {
2170         MAILSTREAM *stream = NIL;
2171         long debug;
2172         char tmp[256];
2173         
2174         if (!vms) {
2175                 ast_log(LOG_ERROR, "vm_state is NULL!\n");
2176                 return -1;
2177         }
2178         if (option_debug > 2)
2179                 ast_log(LOG_DEBUG, "vm_state user is:%s\n", vms->imapuser);
2180         if (vms->mailstream == NIL || !vms->mailstream) {
2181                 if (option_debug)
2182                         ast_log(LOG_DEBUG, "mailstream not set.\n");
2183         } else {
2184                 stream = vms->mailstream;
2185         }
2186         /* debug = T;  user wants protocol telemetry? */
2187         debug = NIL;  /* NO protocol telemetry? */
2188
2189         if (delimiter == '\0') {                /* did not probe the server yet */
2190                 char *cp;
2191 #ifdef USE_SYSTEM_IMAP
2192 #include <imap/linkage.c>
2193 #elif defined(USE_SYSTEM_CCLIENT)
2194 #include <c-client/linkage.c>
2195 #else
2196 #include "linkage.c"
2197 #endif
2198                 /* Connect to INBOX first to get folders delimiter */
2199                 imap_mailbox_name(tmp, sizeof(tmp), vms, 0, 1);
2200                 ast_mutex_lock(&vms->lock);
2201                 stream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2202                 ast_mutex_unlock(&vms->lock);
2203                 if (stream == NIL) {
2204                         ast_log(LOG_ERROR, "Can't connect to imap server %s\n", tmp);
2205                         return -1;
2206                 }
2207                 get_mailbox_delimiter(stream);
2208                 /* update delimiter in imapfolder */
2209                 for (cp = imapfolder; *cp; cp++)
2210                         if (*cp == '/')
2211                                 *cp = delimiter;
2212         }
2213         /* Now connect to the target folder */
2214         imap_mailbox_name(tmp, sizeof(tmp), vms, box, 1);
2215         if (option_debug > 2)
2216                 ast_log(LOG_DEBUG, "Before mail_open, server: %s, box:%d\n", tmp, box);
2217         ast_mutex_lock(&vms->lock);
2218         vms->mailstream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2219         ast_mutex_unlock(&vms->lock);
2220         if (vms->mailstream == NIL) {
2221                 return -1;
2222         } else {
2223                 return 0;
2224         }
2225 }
2226
2227 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box)
2228 {
2229         SEARCHPGM *pgm;
2230         SEARCHHEADER *hdr;
2231         int ret, urgent = 0;
2232
2233         /* If Urgent, then look at INBOX */
2234         if (box == 11) {
2235                 box = NEW_FOLDER;
2236                 urgent = 1;
2237         }
2238
2239         ast_copy_string(vms->imapuser, vmu->imapuser, sizeof(vms->imapuser));
2240         ast_debug(3, "Before init_mailstream, user is %s\n", vmu->imapuser);
2241
2242         if ((ret = init_mailstream(vms, box)) || !vms->mailstream) {
2243                 ast_log(AST_LOG_ERROR, "Could not initialize mailstream\n");
2244                 return -1;
2245         }
2246         
2247         create_dirpath(vms->curdir, sizeof(vms->curdir), vmu->context, vms->username, vms->curbox);
2248         
2249         /* Check Quota */
2250         if  (box == 0)  {
2251                 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mbox(box));
2252                 check_quota(vms, (char *) mbox(box));
2253         }
2254
2255         ast_mutex_lock(&vms->lock);
2256         pgm = mail_newsearchpgm();
2257
2258         /* Check IMAP folder for Asterisk messages only... */
2259         hdr = mail_newsearchheader("X-Asterisk-VM-Extension", (!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : vmu->mailbox));
2260         hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", vmu->context);
2261         pgm->header = hdr;
2262         pgm->deleted = 0;
2263         pgm->undeleted = 1;
2264
2265         /* if box = NEW_FOLDER, check for new, if box = OLD_FOLDER, check for read */
2266         if (box == NEW_FOLDER && urgent == 1) {
2267                 pgm->unseen = 1;
2268                 pgm->seen = 0;
2269                 pgm->flagged = 1;
2270                 pgm->unflagged = 0;
2271         } else if (box == NEW_FOLDER && urgent == 0) {
2272                 pgm->unseen = 1;
2273                 pgm->seen = 0;
2274                 pgm->flagged = 0;
2275                 pgm->unflagged = 1;
2276         } else if (box == OLD_FOLDER) {
2277                 pgm->seen = 1;
2278                 pgm->unseen = 0;
2279         }
2280
2281         ast_debug(3, "Before mail_search_full, user is %s\n", vmu->imapuser);
2282
2283         vms->vmArrayIndex = 0;
2284         mail_search_full (vms->mailstream, NULL, pgm, NIL);
2285         vms->lastmsg = vms->vmArrayIndex - 1;
2286         mail_free_searchpgm(&pgm);
2287
2288         ast_mutex_unlock(&vms->lock);
2289         return 0;
2290 }
2291
2292 static void write_file(char *filename, char *buffer, unsigned long len)
2293 {
2294         FILE *output;
2295
2296         output = fopen (filename, "w");
2297         if (fwrite(buffer, len, 1, output) != 1) {
2298                 if (ferror(output)) {
2299                         ast_log(LOG_ERROR, "Short write while writing e-mail body: %s.\n", strerror(errno));
2300                 }
2301         }
2302         fclose (output);
2303 }
2304
2305 static void update_messages_by_imapuser(const char *user, unsigned long number)
2306 {
2307         struct vm_state *vms = get_vm_state_by_imapuser(user, 1);
2308
2309         if (!vms && !(vms = get_vm_state_by_imapuser(user, 0))) {
2310                 return;
2311         }
2312
2313         ast_debug(3, "saving mailbox message number %lu as message %d. Interactive set to %d\n", number, vms->vmArrayIndex, vms->interactive);
2314         vms->msgArray[vms->vmArrayIndex++] = number;
2315 }
2316
2317 void mm_searched(MAILSTREAM *stream, unsigned long number)
2318 {
2319         char *mailbox = stream->mailbox, buf[1024] = "", *user;
2320
2321         if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))))
2322                 return;
2323
2324         update_messages_by_imapuser(user, number);
2325 }
2326
2327 static struct ast_vm_user *find_user_realtime_imapuser(const char *imapuser)
2328 {
2329         struct ast_variable *var;
2330         struct ast_vm_user *vmu;
2331
2332         vmu = ast_calloc(1, sizeof *vmu);
2333         if (!vmu)
2334                 return NULL;
2335         ast_set_flag(vmu, VM_ALLOCED);
2336         populate_defaults(vmu);
2337
2338         var = ast_load_realtime("voicemail", "imapuser", imapuser, NULL);
2339         if (var) {
2340                 apply_options_full(vmu, var);
2341                 ast_variables_destroy(var);
2342                 return vmu;
2343         } else {
2344                 free(vmu);
2345                 return NULL;
2346         }
2347 }
2348
2349 /* Interfaces to C-client */
2350
2351 void mm_exists(MAILSTREAM * stream, unsigned long number)
2352 {
2353         /* mail_ping will callback here if new mail! */
2354         ast_debug(4, "Entering EXISTS callback for message %ld\n", number);
2355         if (number == 0) return;
2356         set_update(stream);
2357 }
2358
2359
2360 void mm_expunged(MAILSTREAM * stream, unsigned long number)
2361 {
2362         /* mail_ping will callback here if expunged mail! */
2363         ast_debug(4, "Entering EXPUNGE callback for message %ld\n", number);
2364         if (number == 0) return;
2365         set_update(stream);
2366 }
2367
2368
2369 void mm_flags(MAILSTREAM * stream, unsigned long number)
2370 {
2371         /* mail_ping will callback here if read mail! */
2372         ast_debug(4, "Entering FLAGS callback for message %ld\n", number);
2373         if (number == 0) return;
2374         set_update(stream);
2375 }
2376
2377
2378 void mm_notify(MAILSTREAM * stream, char *string, long errflg)
2379 {
2380         ast_debug(5, "Entering NOTIFY callback, errflag is %ld, string is %s\n", errflg, string);
2381         mm_log (string, errflg);
2382 }
2383
2384
2385 void mm_list(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2386 {
2387         if (delimiter == '\0') {
2388                 delimiter = delim;
2389         }
2390
2391         ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
2392         if (attributes & LATT_NOINFERIORS)
2393                 ast_debug(5, "no inferiors\n");
2394         if (attributes & LATT_NOSELECT)
2395                 ast_debug(5, "no select\n");
2396         if (attributes & LATT_MARKED)
2397                 ast_debug(5, "marked\n");
2398         if (attributes & LATT_UNMARKED)
2399                 ast_debug(5, "unmarked\n");
2400 }
2401
2402
2403 void mm_lsub(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2404 {
2405         ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
2406         if (attributes & LATT_NOINFERIORS)
2407                 ast_debug(5, "no inferiors\n");
2408         if (attributes & LATT_NOSELECT)
2409                 ast_debug(5, "no select\n");
2410         if (attributes & LATT_MARKED)
2411                 ast_debug(5, "marked\n");
2412         if (attributes & LATT_UNMARKED)
2413                 ast_debug(5, "unmarked\n");
2414 }
2415
2416
2417 void mm_status(MAILSTREAM * stream, char *mailbox, MAILSTATUS * status)
2418 {
2419         ast_log(AST_LOG_NOTICE, " Mailbox %s", mailbox);
2420         if (status->flags & SA_MESSAGES)
2421                 ast_log(AST_LOG_NOTICE, ", %lu messages", status->messages);
2422         if (status->flags & SA_RECENT)
2423                 ast_log(AST_LOG_NOTICE, ", %lu recent", status->recent);
2424         if (status->flags & SA_UNSEEN)
2425                 ast_log(AST_LOG_NOTICE, ", %lu unseen", status->unseen);
2426         if (status->flags & SA_UIDVALIDITY)
2427                 ast_log(AST_LOG_NOTICE, ", %lu UID validity", status->uidvalidity);
2428         if (status->flags & SA_UIDNEXT)
2429                 ast_log(AST_LOG_NOTICE, ", %lu next UID", status->uidnext);
2430         ast_log(AST_LOG_NOTICE, "\n");
2431 }
2432
2433
2434 void mm_log(char *string, long errflg)
2435 {
2436         switch ((short) errflg) {
2437                 case NIL:
2438                         ast_debug(1, "IMAP Info: %s\n", string);
2439                         break;
2440                 case PARSE:
2441                 case WARN:
2442                         ast_log(AST_LOG_WARNING, "IMAP Warning: %s\n", string);
2443                         break;
2444                 case ERROR:
2445                         ast_log(AST_LOG_ERROR, "IMAP Error: %s\n", string);
2446                         break;
2447         }
2448 }
2449
2450
2451 void mm_dlog(char *string)
2452 {
2453         ast_log(AST_LOG_NOTICE, "%s\n", string);
2454 }
2455
2456
2457 void mm_login(NETMBX * mb, char *user, char *pwd, long trial)
2458 {
2459         struct ast_vm_user *vmu;
2460
2461         ast_debug(4, "Entering callback mm_login\n");
2462
2463         ast_copy_string(user, mb->user, MAILTMPLEN);
2464
2465         /* We should only do this when necessary */
2466         if (!ast_strlen_zero(authpassword)) {
2467                 ast_copy_string(pwd, authpassword, MAILTMPLEN);
2468         } else {
2469                 AST_LIST_TRAVERSE(&users, vmu, list) {
2470                         if (!strcasecmp(mb->user, vmu->imapuser)) {
2471                                 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2472                                 break;
2473                         }
2474                 }
2475                 if (!vmu) {
2476                         if ((vmu = find_user_realtime_imapuser(mb->user))) {
2477                                 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2478                                 free_user(vmu);
2479                         }
2480                 }
2481         }
2482 }
2483
2484
2485 void mm_critical(MAILSTREAM * stream)
2486 {
2487 }
2488
2489
2490 void mm_nocritical(MAILSTREAM * stream)
2491 {
2492 }
2493
2494
2495 long mm_diskerror(MAILSTREAM * stream, long errcode, long serious)
2496 {
2497         kill (getpid (), SIGSTOP);
2498         return NIL;
2499 }
2500
2501
2502 void mm_fatal(char *string)
2503 {
2504         ast_log(AST_LOG_ERROR, "IMAP access FATAL error: %s\n", string);
2505 }
2506
2507 /* C-client callback to handle quota */
2508 static void mm_parsequota(MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
2509 {
2510         struct vm_state *vms;
2511         char *mailbox = stream->mailbox, *user;
2512         char buf[1024] = "";
2513         unsigned long usage = 0, limit = 0;
2514         
2515         while (pquota) {
2516                 usage = pquota->usage;
2517                 limit = pquota->limit;
2518                 pquota = pquota->next;
2519         }
2520         
2521         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)))) {
2522                 ast_log(AST_LOG_ERROR, "No state found.\n");
2523                 return;
2524         }
2525
2526         ast_debug(3, "User %s usage is %lu, limit is %lu\n", user, usage, limit);
2527
2528         vms->quota_usage = usage;
2529         vms->quota_limit = limit;
2530 }
2531
2532 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len)
2533 {
2534         char *start, *eol_pnt;
2535         int taglen;
2536
2537         if (ast_strlen_zero(header) || ast_strlen_zero(tag))
2538                 return NULL;
2539
2540         taglen = strlen(tag) + 1;
2541         if (taglen < 1)
2542                 return NULL;
2543
2544         if (!(start = strstr(header, tag)))
2545                 return NULL;
2546
2547         /* Since we can be called multiple times we should clear our buffer */
2548         memset(buf, 0, len);
2549
2550         ast_copy_string(buf, start+taglen, len);
2551         if ((eol_pnt = strchr(buf,'\r')) || (eol_pnt = strchr(buf,'\n')))
2552                 *eol_pnt = '\0';
2553         return buf;
2554 }
2555
2556 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len)
2557 {
2558         char *start, *quote, *eol_pnt;
2559
2560         if (ast_strlen_zero(mailbox))
2561                 return NULL;
2562
2563         if (!(start = strstr(mailbox, "/user=")))
2564                 return NULL;
2565
2566         ast_copy_string(buf, start+6, len);
2567
2568         if (!(quote = strchr(buf, '\"'))) {
2569                 if (!(eol_pnt = strchr(buf, '/')))
2570                         eol_pnt = strchr(buf,'}');
2571                 *eol_pnt = '\0';
2572                 return buf;
2573         } else {
2574                 eol_pnt = strchr(buf+1,'\"');
2575                 *eol_pnt = '\0';
2576                 return buf+1;
2577         }
2578 }
2579
2580 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu)
2581 {
2582         struct vm_state *vms_p;
2583
2584         pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2585         if ((vms_p = pthread_getspecific(ts_vmstate.key)) && !strcmp(vms_p->imapuser, vmu->imapuser) && !strcmp(vms_p->username, vmu->mailbox)) {
2586                 return vms_p;
2587         }
2588         if (option_debug > 4)
2589                 ast_log(AST_LOG_DEBUG, "Adding new vmstate for %s\n", vmu->imapuser);
2590         if (!(vms_p = ast_calloc(1, sizeof(*vms_p))))
2591                 return NULL;
2592         ast_copy_string(vms_p->imapuser, vmu->imapuser, sizeof(vms_p->imapuser));
2593         ast_copy_string(vms_p->username, vmu->mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
2594         ast_copy_string(vms_p->context, vmu->context, sizeof(vms_p->context));
2595         vms_p->mailstream = NIL; /* save for access from interactive entry point */
2596         if (option_debug > 4)
2597                 ast_log(AST_LOG_DEBUG, "Copied %s to %s\n", vmu->imapuser, vms_p->imapuser);
2598         vms_p->updated = 1;
2599         /* set mailbox to INBOX! */
2600         ast_copy_string(vms_p->curbox, mbox(0), sizeof(vms_p->curbox));
2601         init_vm_state(vms_p);
2602         vmstate_insert(vms_p);
2603         return vms_p;
2604 }
2605
2606 static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive)
2607 {
2608         struct vmstate *vlist = NULL;
2609
2610         if (interactive) {
2611                 struct vm_state *vms;
2612                 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2613                 vms = pthread_getspecific(ts_vmstate.key);
2614                 return vms;
2615         }
2616
2617         AST_LIST_LOCK(&vmstates);
2618         AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2619                 if (!vlist->vms) {
2620                         ast_debug(3, "error: vms is NULL for %s\n", user);
2621                         continue;
2622                 }
2623                 if (!vlist->vms->imapuser) {
2624                         ast_debug(3, "error: imapuser is NULL for %s\n", user);
2625                         continue;
2626                 }
2627
2628                 if (!strcmp(vlist->vms->imapuser, user) && (interactive == 2 || vlist->vms->interactive == interactive)) {
2629                         AST_LIST_UNLOCK(&vmstates);
2630                         return vlist->vms;
2631                 }
2632         }
2633         AST_LIST_UNLOCK(&vmstates);
2634
2635         ast_debug(3, "%s not found in vmstates\n", user);
2636
2637         return NULL;
2638 }
2639
2640 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive)
2641 {
2642
2643         struct vmstate *vlist = NULL;
2644         const char *local_context = S_OR(context, "default");
2645
2646         if (interactive) {
2647                 struct vm_state *vms;
2648                 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2649                 vms = pthread_getspecific(ts_vmstate.key);
2650                 return vms;
2651         }
2652
2653         AST_LIST_LOCK(&vmstates);
2654         AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2655                 if (!vlist->vms) {
2656                         ast_debug(3, "error: vms is NULL for %s\n", mailbox);
2657                         continue;
2658                 }
2659                 if (!vlist->vms->username || !vlist->vms->context) {
2660                         ast_debug(3, "error: username is NULL for %s\n", mailbox);
2661                         continue;
2662                 }
2663
2664                 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);
2665                 
2666                 if (!strcmp(vlist->vms->username, mailbox) && !strcmp(vlist->vms->context, local_context) && vlist->vms->interactive == interactive) {
2667                         ast_debug(3, "Found it!\n");
2668                         AST_LIST_UNLOCK(&vmstates);
2669                         return vlist->vms;
2670                 }
2671         }
2672         AST_LIST_UNLOCK(&vmstates);
2673
2674         ast_debug(3, "%s not found in vmstates\n", mailbox);
2675
2676         return NULL;
2677 }
2678
2679 static void vmstate_insert(struct vm_state *vms) 
2680 {
2681         struct vmstate *v;
2682         struct vm_state *altvms;
2683
2684         /* If interactive, it probably already exists, and we should
2685            use the one we already have since it is more up to date.
2686            We can compare the username to find the duplicate */
2687         if (vms->interactive == 1) {
2688                 altvms = get_vm_state_by_mailbox(vms->username, vms->context, 0);
2689                 if (altvms) {   
2690                         ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
2691                         vms->newmessages = altvms->newmessages;
2692                         vms->oldmessages = altvms->oldmessages;
2693                         vms->vmArrayIndex = altvms->vmArrayIndex;
2694                         vms->lastmsg = altvms->lastmsg;
2695                         vms->curmsg = altvms->curmsg;
2696                         /* get a pointer to the persistent store */
2697                         vms->persist_vms = altvms;
2698                         /* Reuse the mailstream? */
2699 #ifdef REALLY_FAST_EVEN_IF_IT_MEANS_RESOURCE_LEAKS
2700                         vms->mailstream = altvms->mailstream;
2701 #else
2702                         vms->mailstream = NIL;
2703 #endif
2704                 }
2705                 return;
2706         }
2707
2708         if (!(v = ast_calloc(1, sizeof(*v))))
2709                 return;
2710         
2711         v->vms = vms;
2712
2713         ast_debug(3, "Inserting vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2714
2715         AST_LIST_LOCK(&vmstates);
2716         AST_LIST_INSERT_TAIL(&vmstates, v, list);
2717         AST_LIST_UNLOCK(&vmstates);
2718 }
2719
2720 static void vmstate_delete(struct vm_state *vms) 
2721 {
2722         struct vmstate *vc = NULL;
2723         struct vm_state *altvms = NULL;
2724
2725         /* If interactive, we should copy pertinent info
2726            back to the persistent state (to make update immediate) */
2727         if (vms->interactive == 1 && (altvms = vms->persist_vms)) {
2728                 ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
2729                 altvms->newmessages = vms->newmessages;
2730                 altvms->oldmessages = vms->oldmessages;
2731                 altvms->updated = 1;
2732                 vms->mailstream = mail_close(vms->mailstream);
2733
2734                 /* Interactive states are not stored within the persistent list */
2735                 return;
2736         }
2737         
2738         ast_debug(3, "Removing vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2739         
2740         AST_LIST_LOCK(&vmstates);
2741         AST_LIST_TRAVERSE_SAFE_BEGIN(&vmstates, vc, list) {
2742                 if (vc->vms == vms) {
2743                         AST_LIST_REMOVE_CURRENT(list);
2744                         break;
2745                 }
2746         }
2747         AST_LIST_TRAVERSE_SAFE_END
2748         AST_LIST_UNLOCK(&vmstates);
2749         
2750         if (vc) {
2751                 ast_mutex_destroy(&vc->vms->lock);
2752                 ast_free(vc);
2753         }
2754         else
2755                 ast_log(AST_LOG_ERROR, "No vmstate found for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2756 }
2757
2758 static void set_update(MAILSTREAM * stream) 
2759 {
2760         struct vm_state *vms;
2761         char *mailbox = stream->mailbox, *user;
2762         char buf[1024] = "";
2763
2764         if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || !(vms = get_vm_state_by_imapuser(user, 0))) {
2765                 if (user && option_debug > 2)
2766                         ast_log(AST_LOG_WARNING, "User %s mailbox not found for update.\n", user);
2767                 return;
2768         }
2769
2770         ast_debug(3, "User %s mailbox set for update.\n", user);
2771
2772         vms->updated = 1; /* Set updated flag since mailbox changed */
2773 }
2774
2775 static void init_vm_state(struct vm_state *vms) 
2776 {
2777         int x;
2778         vms->vmArrayIndex = 0;
2779         for (x = 0; x < VMSTATE_MAX_MSG_ARRAY; x++) {
2780                 vms->msgArray[x] = 0;
2781         }
2782         ast_mutex_init(&vms->lock);
2783 }
2784
2785 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro) 
2786 {
2787         char *body_content;
2788         char *body_decoded;
2789         char *fn = is_intro ? vms->introfn : vms->fn;
2790         unsigned long len;
2791         unsigned long newlen;
2792         char filename[256];
2793         
2794         if (!body || body == NIL)
2795                 return -1;
2796
2797         ast_mutex_lock(&vms->lock);
2798         body_content = mail_fetchbody(vms->mailstream, vms->msgArray[vms->curmsg], section, &len);
2799         ast_mutex_unlock(&vms->lock);
2800         if (body_content != NIL) {
2801                 snprintf(filename, sizeof(filename), "%s.%s", fn, format);
2802                 /* ast_debug(1,body_content); */
2803                 body_decoded = rfc822_base64((unsigned char *) body_content, len, &newlen);
2804                 /* If the body of the file is empty, return an error */
2805                 if (!newlen) {
2806                         return -1;
2807                 }
2808                 write_file(filename, (char *) body_decoded, newlen);
2809         } else {
2810                 ast_debug(5, "Body of message is NULL.\n");
2811                 return -1;
2812         }
2813         return 0;
2814 }
2815
2816 /*! 
2817  * \brief Get delimiter via mm_list callback 
2818  * \param stream
2819  *
2820  * Determines the delimiter character that is used by the underlying IMAP based mail store.
2821  */
2822 /* MUTEX should already be held */
2823 static void get_mailbox_delimiter(MAILSTREAM *stream) {
2824         char tmp[50];
2825         snprintf(tmp, sizeof(tmp), "{%s}", imapserver);
2826         mail_list(stream, tmp, "*");
2827 }
2828
2829 /*! 
2830  * \brief Check Quota for user 
2831  * \param vms a pointer to a vm_state struct, will use the mailstream property of this.
2832  * \param mailbox the mailbox to check the quota for.
2833  *
2834  * Calls imap_getquotaroot, which will populate its results into the vm_state vms input structure.
2835  */
2836 static void check_quota(struct vm_state *vms, char *mailbox) {
2837         ast_mutex_lock(&vms->lock);
2838         mail_parameters(NULL, SET_QUOTA, (void *) mm_parsequota);
2839         ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mailbox);
2840         if (vms && vms->mailstream != NULL) {
2841                 imap_getquotaroot(vms->mailstream, mailbox);
2842         } else {
2843                 ast_log(AST_LOG_WARNING, "Mailstream not available for mailbox: %s\n", mailbox);
2844         }
2845         ast_mutex_unlock(&vms->lock);
2846 }
2847
2848 #endif /* IMAP_STORAGE */
2849
2850 /*! \brief Lock file path
2851     only return failure if ast_lock_path returns 'timeout',
2852    not if the path does not exist or any other reason
2853 */
2854 static int vm_lock_path(const char *path)
2855 {
2856         switch (ast_lock_path(path)) {
2857         case AST_LOCK_TIMEOUT:
2858                 return -1;
2859         default:
2860                 return 0;
2861         }
2862 }
2863
2864
2865 #ifdef ODBC_STORAGE
2866 struct generic_prepare_struct {
2867         char *sql;
2868         int argc;
2869         char **argv;
2870 };
2871
2872 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
2873 {
2874         struct generic_prepare_struct *gps = data;
2875         int res, i;
2876         SQLHSTMT stmt;
2877
2878         res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
2879         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2880                 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
2881                 return NULL;
2882         }
2883         res = SQLPrepare(stmt, (unsigned char *) gps->sql, SQL_NTS);
2884         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2885                 ast_log(AST_LOG_WARNING, "SQL Prepare failed![%s]\n", gps->sql);
2886                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
2887                 return NULL;
2888         }
2889         for (i = 0; i < gps->argc; i++)
2890                 SQLBindParameter(stmt, i + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->argv[i]), 0, gps->argv[i], 0, NULL);
2891
2892         return stmt;
2893 }
2894
2895 /*!
2896  * \brief Retrieves a file from an ODBC data store.
2897  * \param dir the path to the file to be retreived.
2898  * \param msgnum the message number, such as within a mailbox folder.
2899  * 
2900  * This method is used by the RETRIEVE macro when mailboxes are stored in an ODBC back end.
2901  * 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.
2902  *
2903  * The file is looked up by invoking a SQL on the odbc_table (default 'voicemessages') using the dir and msgnum input parameters.
2904  * The output is the message information file with the name msgnum and the extension .txt
2905  * and the message file with the extension of its format, in the directory with base file name of the msgnum.
2906  * 
2907  * \return 0 on success, -1 on error.
2908  */
2909 static int retrieve_file(char *dir, int msgnum)
2910 {
2911         int x = 0;
2912         int res;
2913         int fd = -1;
2914         size_t fdlen = 0;
2915         void *fdm = MAP_FAILED;
2916         SQLSMALLINT colcount = 0;
2917         SQLHSTMT stmt;
2918         char sql[PATH_MAX];
2919         char fmt[80]="";
2920         char *c;
2921         char coltitle[256];
2922         SQLSMALLINT collen;
2923         SQLSMALLINT datatype;
2924         SQLSMALLINT decimaldigits;
2925         SQLSMALLINT nullable;
2926         SQLULEN colsize;
2927         SQLLEN colsize2;
2928         FILE *f = NULL;
2929         char rowdata[80];
2930         char fn[PATH_MAX];
2931         char full_fn[PATH_MAX];
2932         char msgnums[80];
2933         char *argv[] = { dir, msgnums };
2934         struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
2935
2936         struct odbc_obj *obj;
2937         obj = ast_odbc_request_obj(odbc_database, 0);
2938         if (obj) {
2939                 ast_copy_string(fmt, vmfmts, sizeof(fmt));
2940                 c = strchr(fmt, '|');
2941                 if (c)
2942                         *c = '\0';
2943                 if (!strcasecmp(fmt, "wav49"))
2944                         strcpy(fmt, "WAV");
2945                 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
2946                 if (msgnum > -1)
2947                         make_file(fn, sizeof(fn), dir, msgnum);
2948                 else
2949                         ast_copy_string(fn, dir, sizeof(fn));
2950
2951                 /* Create the information file */
2952                 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
2953                 
2954                 if (!(f = fopen(full_fn, "w+"))) {
2955                         ast_log(AST_LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
2956                         goto yuck;
2957                 }
2958                 
2959                 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
2960                 snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?", odbc_table);
2961                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2962                 if (!stmt) {
2963                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2964                         ast_odbc_release_obj(obj);
2965                         goto yuck;
2966                 }
2967                 res = SQLFetch(stmt);
2968                 if (res == SQL_NO_DATA) {
2969                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2970                         ast_odbc_release_obj(obj);
2971                         goto yuck;
2972                 } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2973                         ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2974                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2975                         ast_odbc_release_obj(obj);
2976                         goto yuck;
2977                 }
2978                 fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
2979                 if (fd < 0) {
2980                         ast_log(AST_LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
2981                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2982                         ast_odbc_release_obj(obj);
2983                         goto yuck;
2984                 }
2985                 res = SQLNumResultCols(stmt, &colcount);
2986                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {   
2987                         ast_log(AST_LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
2988                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2989                         ast_odbc_release_obj(obj);
2990                         goto yuck;
2991                 }
2992                 if (f) 
2993                         fprintf(f, "[message]\n");
2994                 for (x = 0; x < colcount; x++) {
2995                         rowdata[0] = '\0';
2996                         collen = sizeof(coltitle);
2997                         res = SQLDescribeCol(stmt, x + 1, (unsigned char *) coltitle, sizeof(coltitle), &collen, 
2998                                                 &datatype, &colsize, &decimaldigits, &nullable);
2999                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3000                                 ast_log(AST_LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
3001                                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3002                                 ast_odbc_release_obj(obj);
3003                                 goto yuck;
3004                         }
3005                         if (!strcasecmp(coltitle, "recording")) {
3006                                 off_t offset;
3007                                 res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
3008                                 fdlen = colsize2;
3009                                 if (fd > -1) {
3010                                         char tmp[1]="";
3011                                         lseek(fd, fdlen - 1, SEEK_SET);
3012                                         if (write(fd, tmp, 1) != 1) {
3013                                                 close(fd);
3014                                                 fd = -1;
3015                                                 continue;
3016                                         }
3017                                         /* Read out in small chunks */
3018                                         for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
3019                                                 if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
3020                                                         ast_log(AST_LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
3021                                                         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3022                                                         ast_odbc_release_obj(obj);
3023                                                         goto yuck;
3024                                                 } else {
3025                                                         res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
3026                                                         munmap(fdm, CHUNKSIZE);
3027                                                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3028                                                                 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3029                                                                 unlink(full_fn);
3030                                                                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3031                                                                 ast_odbc_release_obj(obj);
3032                                                                 goto yuck;
3033                                                         }
3034                                                 }
3035                                         }
3036                                         if (truncate(full_fn, fdlen) < 0) {
3037                                                 ast_log(LOG_WARNING, "Unable to truncate '%s': %s\n", full_fn, strerror(errno));
3038                                         }
3039                                 }
3040                         } else {
3041                                 res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3042                                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3043                                         ast_log(AST_LOG_WARNING, "SQL Get Data error! coltitle=%s\n[%s]\n\n", coltitle, sql);
3044                                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3045                                         ast_odbc_release_obj(obj);
3046                                         goto yuck;
3047                                 }
3048                                 if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
3049                                         fprintf(f, "%s=%s\n", coltitle, rowdata);
3050                         }
3051                 }
3052                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3053                 ast_odbc_release_obj(obj);
3054         } else
3055                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3056 yuck:   
3057         if (f)
3058                 fclose(f);
3059         if (fd > -1)
3060                 close(fd);
3061         return x - 1;
3062 }
3063
3064 /*!
3065  * \brief Determines the highest message number in use for a given user and mailbox folder.
3066  * \param vmu 
3067  * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
3068  *
3069  * This method is used when mailboxes are stored in an ODBC back end.
3070  * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
3071  *
3072  * \return the value of zero or greaterto indicate the last message index in use, -1 to indicate none.
3073  */
3074 static int last_message_index(struct ast_vm_user *vmu, char *dir)
3075 {
3076         int x = 0;
3077         int res;
3078         SQLHSTMT stmt;
3079         char sql[PATH_MAX];
3080         char rowdata[20];
3081         char *argv[] = { dir };
3082         struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
3083
3084         struct odbc_obj *obj;
3085         obj = ast_odbc_request_obj(odbc_database, 0);
3086         if (obj) {
3087                 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?", odbc_table);
3088                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3089                 if (!stmt) {
3090                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3091                         ast_odbc_release_obj(obj);
3092                         goto yuck;
3093                 }
3094                 res = SQLFetch(stmt);
3095                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3096                         ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3097                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3098                         ast_odbc_release_obj(obj);
3099                         goto yuck;
3100                 }
3101                 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3102                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3103                         ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3104                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3105                         ast_odbc_release_obj(obj);
3106                         goto yuck;
3107                 }
3108                 if (sscanf(rowdata, "%d", &x) != 1)
3109                         ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
3110                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3111                 ast_odbc_release_obj(obj);
3112         } else
3113                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3114 yuck:   
3115         return x - 1;
3116 }
3117
3118 /*!
3119  * \brief Determines if the specified message exists.
3120  * \param dir the folder the mailbox folder to look for messages. 
3121  * \param msgnum the message index to query for.
3122  *
3123  * This method is used when mailboxes are stored in an ODBC back end.
3124  *
3125  * \return greater than zero if the message exists, zero when the message does not exist or on error.