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