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