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