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