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