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