Or not.
[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_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
780 #endif
781         } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
782                 ast_set2_flag(vmu, ast_true(value), VM_DELETE); 
783         } else if (!strcasecmp(var, "saycid")){
784                 ast_set2_flag(vmu, ast_true(value), VM_SAYCID); 
785         } else if (!strcasecmp(var,"sendvoicemail")){
786                 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL); 
787         } else if (!strcasecmp(var, "review")){
788                 ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
789         } else if (!strcasecmp(var, "tempgreetwarn")){
790                 ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);  
791         } else if (!strcasecmp(var, "messagewrap")){
792                 ast_set2_flag(vmu, ast_true(value), VM_MESSAGEWRAP);    
793         } else if (!strcasecmp(var, "operator")) {
794                 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);       
795         } else if (!strcasecmp(var, "envelope")){
796                 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);       
797         } else if (!strcasecmp(var, "moveheard")){
798                 ast_set2_flag(vmu, ast_true(value), VM_MOVEHEARD);
799         } else if (!strcasecmp(var, "sayduration")){
800                 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);    
801         } else if (!strcasecmp(var, "saydurationm")){
802                 if (sscanf(value, "%d", &x) == 1) {
803                         vmu->saydurationm = x;
804                 } else {
805                         ast_log(AST_LOG_WARNING, "Invalid min duration for say duration\n");
806                 }
807         } else if (!strcasecmp(var, "forcename")){
808                 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);      
809         } else if (!strcasecmp(var, "forcegreetings")){
810                 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);     
811         } else if (!strcasecmp(var, "callback")) {
812                 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
813         } else if (!strcasecmp(var, "dialout")) {
814                 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
815         } else if (!strcasecmp(var, "exitcontext")) {
816                 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
817         } else if (!strcasecmp(var, "maxmessage") || !strcasecmp(var, "maxsecs")) {
818                 if (vmu->maxsecs <= 0) {
819                         ast_log(AST_LOG_WARNING, "Invalid max message length of %s. Using global value %d\n", value, vmmaxsecs);
820                         vmu->maxsecs = vmmaxsecs;
821                 } else {
822                         vmu->maxsecs = atoi(value);
823                 }
824                 if (!strcasecmp(var, "maxmessage"))
825                         ast_log(AST_LOG_WARNING, "Option 'maxmessage' has been deprecated in favor of 'maxsecs'.  Please make that change in your voicemail config.\n");
826         } else if (!strcasecmp(var, "maxmsg")) {
827                 vmu->maxmsg = atoi(value);
828                 if (vmu->maxmsg <= 0) {
829                         ast_log(AST_LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %d\n", value, MAXMSG);
830                         vmu->maxmsg = MAXMSG;
831                 } else if (vmu->maxmsg > MAXMSGLIMIT) {
832                         ast_log(AST_LOG_WARNING, "Maximum number of messages per folder is %d. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
833                         vmu->maxmsg = MAXMSGLIMIT;
834                 }
835         } else if (!strcasecmp(var, "backupdeleted")) {
836                 if (sscanf(value, "%d", &x) == 1)
837                         vmu->maxdeletedmsg = x;
838                 else if (ast_true(value))
839                         vmu->maxdeletedmsg = MAXMSG;
840                 else
841                         vmu->maxdeletedmsg = 0;
842
843                 if (vmu->maxdeletedmsg < 0) {
844                         ast_log(AST_LOG_WARNING, "Invalid number of deleted messages saved per mailbox backupdeleted=%s. Using default value %d\n", value, MAXMSG);
845                         vmu->maxdeletedmsg = MAXMSG;
846                 } else if (vmu->maxdeletedmsg > MAXMSGLIMIT) {
847                         ast_log(AST_LOG_WARNING, "Maximum number of deleted messages saved per mailbox is %d. Cannot accept value backupdeleted=%s\n", MAXMSGLIMIT, value);
848                         vmu->maxdeletedmsg = MAXMSGLIMIT;
849                 }
850         } else if (!strcasecmp(var, "volgain")) {
851                 sscanf(value, "%lf", &vmu->volgain);
852         } else if (!strcasecmp(var, "options")) {
853                 apply_options(vmu, value);
854         } else {
855                 ast_log(LOG_WARNING, "Unknown option '%s' specified for mailbox '%s'.\n", var, vmu->mailbox);
856         }
857 }
858
859 static char *vm_check_password_shell(char *command, char *buf, size_t len) 
860 {
861         int fds[2], pid = 0;
862
863         memset(buf, 0, len);
864
865         if (pipe(fds)) {
866                 snprintf(buf, len, "FAILURE: Pipe failed: %s", strerror(errno));
867         } else {
868                 /* good to go*/
869                 pid = ast_safe_fork(0);
870
871                 if (pid < 0) {
872                         /* ok maybe not */
873                         close(fds[0]);
874                         close(fds[1]);
875                         snprintf(buf, len, "FAILURE: Fork failed");
876                 } else if (pid) {
877                         /* parent */
878                         close(fds[1]);
879                         read(fds[0], buf, len);
880                         close(fds[0]);
881                 } else {
882                         /*  child */
883                         AST_DECLARE_APP_ARGS(arg,
884                                 AST_APP_ARG(v)[20];
885                         );
886                         char *mycmd = ast_strdupa(command);
887
888                         close(fds[0]);
889                         dup2(fds[1], STDOUT_FILENO);
890                         close(fds[1]);
891                         ast_close_fds_above_n(STDOUT_FILENO);
892
893                         AST_NONSTANDARD_APP_ARGS(arg, mycmd, ' ');
894
895                         execv(arg.v[0], arg.v); 
896                         printf("FAILURE: %s", strerror(errno));
897                         _exit(0);
898                 }
899         }
900         return buf;
901 }
902
903 /*!
904  * \brief Check that password meets minimum required length
905  * \param vmu The voicemail user to change the password for.
906  * \param password The password string to check
907  *
908  * \return zero on ok, 1 on not ok.
909  */
910 static int check_password(struct ast_vm_user *vmu, char *password)
911 {
912         /* check minimum length */
913         if (strlen(password) < minpassword)
914                 return 1;
915         if (!ast_strlen_zero(ext_pass_check_cmd)) {
916                 char cmd[255], buf[255];
917
918                 ast_log(AST_LOG_DEBUG, "Verify password policies for %s\n", password);
919
920                 snprintf(cmd, sizeof(cmd), "%s %s %s %s %s", ext_pass_check_cmd, vmu->mailbox, vmu->context, vmu->password, password);
921                 if (vm_check_password_shell(cmd, buf, sizeof(buf))) {
922                         ast_debug(5, "Result: %s\n", buf);
923                         if (!strncasecmp(buf, "VALID", 5)) {
924                                 ast_debug(3, "Passed password check: '%s'\n", buf);
925                                 return 0;
926                         } else if (!strncasecmp(buf, "FAILURE", 7)) {
927                                 ast_log(AST_LOG_WARNING, "Unable to execute password validation script: '%s'.\n", buf);
928                                 return 0;
929                         } else {
930                                 ast_log(AST_LOG_NOTICE, "Password doesn't match policies for user %s %s\n", vmu->mailbox, password);
931                                 return 1;
932                         }
933                 }
934         }
935         return 0;
936 }
937
938 /*! 
939  * \brief Performs a change of the voicemail passowrd in the realtime engine.
940  * \param vmu The voicemail user to change the password for.
941  * \param password The new value to be set to the password for this user.
942  * 
943  * This only works if the voicemail user has a unique id, and if there is a realtime engine configured.
944  * This is called from the (top level) vm_change_password.
945  *
946  * \return zero on success, -1 on error.
947  */
948 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
949 {
950         int res;
951         if (!ast_strlen_zero(vmu->uniqueid)) {
952                 if (strlen(password) > 10) {
953                         ast_realtime_require_field("voicemail", "password", RQ_CHAR, strlen(password), SENTINEL);
954                 }
955                 res = ast_update_realtime("voicemail", "uniqueid", vmu->uniqueid, "password", password, SENTINEL);
956                 if (res > 0) {
957                         ast_copy_string(vmu->password, password, sizeof(vmu->password));
958                         res = 0;
959                 } else if (!res) {
960                         res = -1;
961                 }
962                 return res;
963         }
964         return -1;
965 }
966
967 /*!
968  * \brief Destructively Parse options and apply.
969  */
970 static void apply_options(struct ast_vm_user *vmu, const char *options)
971 {       
972         char *stringp;
973         char *s;
974         char *var, *value;
975         stringp = ast_strdupa(options);
976         while ((s = strsep(&stringp, "|"))) {
977                 value = s;
978                 if ((var = strsep(&value, "=")) && value) {
979                         apply_option(vmu, var, value);
980                 }
981         }       
982 }
983
984 /*!
985  * \brief Loads the options specific to a voicemail user.
986  * 
987  * This is called when a vm_user structure is being set up, such as from load_options.
988  */
989 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
990 {
991         struct ast_variable *tmp;
992         tmp = var;
993         while (tmp) {
994                 if (!strcasecmp(tmp->name, "vmsecret")) {
995                         ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
996                 } else if (!strcasecmp(tmp->name, "secret") || !strcasecmp(tmp->name, "password")) { /* don't overwrite vmsecret if it exists */
997                         if (ast_strlen_zero(retval->password))
998                                 ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
999                 } else if (!strcasecmp(tmp->name, "uniqueid")) {
1000                         ast_copy_string(retval->uniqueid, tmp->value, sizeof(retval->uniqueid));
1001                 } else if (!strcasecmp(tmp->name, "pager")) {
1002                         ast_copy_string(retval->pager, tmp->value, sizeof(retval->pager));
1003                 } else if (!strcasecmp(tmp->name, "email")) {
1004                         ast_copy_string(retval->email, tmp->value, sizeof(retval->email));
1005                 } else if (!strcasecmp(tmp->name, "fullname")) {
1006                         ast_copy_string(retval->fullname, tmp->value, sizeof(retval->fullname));
1007                 } else if (!strcasecmp(tmp->name, "context")) {
1008                         ast_copy_string(retval->context, tmp->value, sizeof(retval->context));
1009 #ifdef IMAP_STORAGE
1010                 } else if (!strcasecmp(tmp->name, "imapuser")) {
1011                         ast_copy_string(retval->imapuser, tmp->value, sizeof(retval->imapuser));
1012                 } else if (!strcasecmp(tmp->name, "imappassword")) {
1013                         ast_copy_string(retval->imappassword, tmp->value, sizeof(retval->imappassword));
1014                 } else if (!strcasecmp(tmp->name, "imapsecret")) {
1015                         ast_copy_string(retval->imappassword, tmp->value, sizeof(retval->imappassword));
1016 #endif
1017                 } else
1018                         apply_option(retval, tmp->name, tmp->value);
1019                 tmp = tmp->next;
1020         } 
1021 }
1022
1023 /*!
1024  * \brief Determines if a DTMF key entered is valid.
1025  * \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.
1026  *
1027  * Tests the character entered against the set of valid DTMF characters. 
1028  * \return 1 if the character entered is a valid DTMF digit, 0 if the character is invalid.
1029  */
1030 static int is_valid_dtmf(const char *key)
1031 {
1032         int i;
1033         char *local_key = ast_strdupa(key);
1034
1035         for (i = 0; i < strlen(key); ++i) {
1036                 if (!strchr(VALID_DTMF, *local_key)) {
1037                         ast_log(AST_LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
1038                         return 0;
1039                 }
1040                 local_key++;
1041         }
1042         return 1;
1043 }
1044
1045 /*!
1046  * \brief Finds a voicemail user from the realtime engine.
1047  * \param ivm
1048  * \param context
1049  * \param mailbox
1050  *
1051  * 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.
1052  *
1053  * \return The ast_vm_user structure for the user that was found.
1054  */
1055 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1056 {
1057         struct ast_variable *var;
1058         struct ast_vm_user *retval;
1059
1060         if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
1061                 if (!ivm)
1062                         ast_set_flag(retval, VM_ALLOCED);       
1063                 else
1064                         memset(retval, 0, sizeof(*retval));
1065                 if (mailbox) 
1066                         ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
1067                 populate_defaults(retval);
1068                 if (!context && ast_test_flag((&globalflags), VM_SEARCH))
1069                         var = ast_load_realtime("voicemail", "mailbox", mailbox, SENTINEL);
1070                 else
1071                         var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, SENTINEL);
1072                 if (var) {
1073                         apply_options_full(retval, var);
1074                         ast_variables_destroy(var);
1075                 } else { 
1076                         if (!ivm) 
1077                                 ast_free(retval);
1078                         retval = NULL;
1079                 }       
1080         } 
1081         return retval;
1082 }
1083
1084 /*!
1085  * \brief Finds a voicemail user from the users file or the realtime engine.
1086  * \param ivm
1087  * \param context
1088  * \param mailbox
1089  * 
1090  * \return The ast_vm_user structure for the user that was found.
1091  */
1092 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1093 {
1094         /* This function could be made to generate one from a database, too */
1095         struct ast_vm_user *vmu=NULL, *cur;
1096         AST_LIST_LOCK(&users);
1097
1098         if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
1099                 context = "default";
1100
1101         AST_LIST_TRAVERSE(&users, cur, list) {
1102                 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
1103                         break;
1104                 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
1105                         break;
1106         }
1107         if (cur) {
1108                 /* Make a copy, so that on a reload, we have no race */
1109                 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
1110                         memcpy(vmu, cur, sizeof(*vmu));
1111                         ast_set2_flag(vmu, !ivm, VM_ALLOCED);
1112                         AST_LIST_NEXT(vmu, list) = NULL;
1113                 }
1114         } else
1115                 vmu = find_user_realtime(ivm, context, mailbox);
1116         AST_LIST_UNLOCK(&users);
1117         return vmu;
1118 }
1119
1120 /*!
1121  * \brief Resets a user password to a specified password.
1122  * \param context
1123  * \param mailbox
1124  * \param newpass
1125  *
1126  * This does the actual change password work, called by the vm_change_password() function.
1127  *
1128  * \return zero on success, -1 on error.
1129  */
1130 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
1131 {
1132         /* This function could be made to generate one from a database, too */
1133         struct ast_vm_user *cur;
1134         int res = -1;
1135         AST_LIST_LOCK(&users);
1136         AST_LIST_TRAVERSE(&users, cur, list) {
1137                 if ((!context || !strcasecmp(context, cur->context)) &&
1138                         (!strcasecmp(mailbox, cur->mailbox)))
1139                                 break;
1140         }
1141         if (cur) {
1142                 ast_copy_string(cur->password, newpass, sizeof(cur->password));
1143                 res = 0;
1144         }
1145         AST_LIST_UNLOCK(&users);
1146         return res;
1147 }
1148
1149 /*! 
1150  * \brief The handler for the change password option.
1151  * \param vmu The voicemail user to work with.
1152  * \param newpassword The new password (that has been gathered from the appropriate prompting).
1153  * 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.
1154  * It is also called when the user wants to change their password from menu option '5' on the mailbox options menu.
1155  */
1156 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
1157 {
1158         struct ast_config   *cfg=NULL;
1159         struct ast_variable *var=NULL;
1160         struct ast_category *cat=NULL;
1161         char *category=NULL, *value=NULL, *new=NULL;
1162         const char *tmp=NULL;
1163         struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
1164         if (!change_password_realtime(vmu, newpassword))
1165                 return;
1166
1167         /* check voicemail.conf */
1168         if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1169                 while ((category = ast_category_browse(cfg, category))) {
1170                         if (!strcasecmp(category, vmu->context)) {
1171                                 if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
1172                                         ast_log(AST_LOG_WARNING, "We could not find the mailbox.\n");
1173                                         break;
1174                                 }
1175                                 value = strstr(tmp,",");
1176                                 if (!value) {
1177                                         ast_log(AST_LOG_WARNING, "variable has bad format.\n");
1178                                         break;
1179                                 }
1180                                 new = alloca((strlen(value)+strlen(newpassword)+1));
1181                                 sprintf(new,"%s%s", newpassword, value);
1182                                 if (!(cat = ast_category_get(cfg, category))) {
1183                                         ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
1184                                         break;
1185                                 }
1186                                 ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
1187                         }
1188                 }
1189                 /* save the results */
1190                 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1191                 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1192                 config_text_file_save(VOICEMAIL_CONFIG, cfg, "AppVoicemail");
1193         }
1194         category = NULL;
1195         var = NULL;
1196         /* check users.conf and update the password stored for the mailbox*/
1197         /* if no vmsecret entry exists create one. */
1198         if ((cfg = ast_config_load("users.conf", config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1199                 ast_debug(4, "we are looking for %s\n", vmu->mailbox);
1200                 while ((category = ast_category_browse(cfg, category))) {
1201                         ast_debug(4, "users.conf: %s\n", category);
1202                         if (!strcasecmp(category, vmu->mailbox)) {
1203                                 if (!(tmp = ast_variable_retrieve(cfg, category, "vmsecret"))) {
1204                                         ast_debug(3, "looks like we need to make vmsecret!\n");
1205                                         var = ast_variable_new("vmsecret", newpassword, "");
1206                                 } 
1207                                 new = alloca(strlen(newpassword)+1);
1208                                 sprintf(new, "%s", newpassword);
1209                                 if (!(cat = ast_category_get(cfg, category))) {
1210                                         ast_debug(4, "failed to get category!\n");
1211                                         break;
1212                                 }
1213                                 if (!var)               
1214                                         ast_variable_update(cat, "vmsecret", new, NULL, 0);
1215                                 else
1216                                         ast_variable_append(cat, var);
1217                         }
1218                 }
1219                 /* save the results and clean things up */
1220                 reset_user_pw(vmu->context, vmu->mailbox, newpassword); 
1221                 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1222                 config_text_file_save("users.conf", cfg, "AppVoicemail");
1223         }
1224 }
1225
1226 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
1227 {
1228         char buf[255];
1229         snprintf(buf,255,"%s %s %s %s",ext_pass_cmd,vmu->context,vmu->mailbox,newpassword);
1230         if (!ast_safe_system(buf)) {
1231                 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1232                 /* Reset the password in memory, too */
1233                 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1234         }
1235 }
1236
1237 /*! 
1238  * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1239  * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1240  * \param len The length of the path string that was written out.
1241  * 
1242  * The path is constructed as 
1243  *      VM_SPOOL_DIRcontext/ext/folder
1244  *
1245  * \return zero on success, -1 on error.
1246  */
1247 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
1248 {
1249         return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
1250 }
1251
1252 /*! 
1253  * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1254  * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1255  * \param len The length of the path string that was written out.
1256  * 
1257  * The path is constructed as 
1258  *      VM_SPOOL_DIRcontext/ext/folder
1259  *
1260  * \return zero on success, -1 on error.
1261  */
1262 static int make_file(char *dest, const int len, const char *dir, const int num)
1263 {
1264         return snprintf(dest, len, "%s/msg%04d", dir, num);
1265 }
1266
1267 /* same as mkstemp, but return a FILE * */
1268 static FILE *vm_mkftemp(char *template)
1269 {
1270         FILE *p = NULL;
1271         int pfd = mkstemp(template);
1272         chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
1273         if (pfd > -1) {
1274                 p = fdopen(pfd, "w+");
1275                 if (!p) {
1276                         close(pfd);
1277                         pfd = -1;
1278                 }
1279         }
1280         return p;
1281 }
1282
1283 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
1284  * \param dest    String. base directory.
1285  * \param len     Length of dest.
1286  * \param context String. Ignored if is null or empty string.
1287  * \param ext     String. Ignored if is null or empty string.
1288  * \param folder  String. Ignored if is null or empty string. 
1289  * \return -1 on failure, 0 on success.
1290  */
1291 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
1292 {
1293         mode_t  mode = VOICEMAIL_DIR_MODE;
1294         int res;
1295
1296         make_dir(dest, len, context, ext, folder);
1297         if ((res = ast_mkdir(dest, mode))) {
1298                 ast_log(AST_LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1299                 return -1;
1300         }
1301         return 0;
1302 }
1303
1304 static const char *mbox(int id)
1305 {
1306         static const char *msgs[] = {
1307 #ifdef IMAP_STORAGE
1308                 imapfolder,
1309 #else
1310                 "INBOX",
1311 #endif
1312                 "Old",
1313                 "Work",
1314                 "Family",
1315                 "Friends",
1316                 "Cust1",
1317                 "Cust2",
1318                 "Cust3",
1319                 "Cust4",
1320                 "Cust5",
1321                 "Deleted",
1322                 "Urgent"
1323         };
1324         return (id >= 0 && id < (sizeof(msgs)/sizeof(msgs[0]))) ? msgs[id] : "Unknown";
1325 }
1326
1327 static void free_user(struct ast_vm_user *vmu)
1328 {
1329         if (ast_test_flag(vmu, VM_ALLOCED))
1330                 ast_free(vmu);
1331 }
1332
1333 /* All IMAP-specific functions should go in this block. This
1334  * keeps them from being spread out all over the code */
1335 #ifdef IMAP_STORAGE
1336 static void vm_imap_delete(int msgnum, struct ast_vm_user *vmu)
1337 {
1338         char arg[10];
1339         struct vm_state *vms;
1340         unsigned long messageNum;
1341
1342         /* Greetings aren't stored in IMAP, so we can't delete them there */
1343         if (msgnum < 0) {
1344                 return;
1345         }
1346
1347         if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1348                 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);
1349                 return;
1350         }
1351
1352         /* find real message number based on msgnum */
1353         /* this may be an index into vms->msgArray based on the msgnum. */
1354         messageNum = vms->msgArray[msgnum];
1355         if (messageNum == 0) {
1356                 ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n",msgnum,messageNum);
1357                 return;
1358         }
1359         if (option_debug > 2)
1360                 ast_log(LOG_DEBUG, "deleting msgnum %d, which is mailbox message %lu\n",msgnum,messageNum);
1361         /* delete message */
1362         snprintf (arg, sizeof(arg), "%lu",messageNum);
1363         mail_setflag (vms->mailstream,arg,"\\DELETED");
1364 }
1365
1366 static int imap_retrieve_greeting (const char *dir, const int msgnum, struct ast_vm_user *vmu)
1367 {
1368         struct vm_state *vms_p;
1369         char *file, *filename;
1370         char *attachment;
1371         int ret = 0, i;
1372         BODY *body;
1373
1374         /* This function is only used for retrieval of IMAP greetings
1375          * regular messages are not retrieved this way, nor are greetings
1376          * if they are stored locally*/
1377         if (msgnum > -1 || !imapgreetings) {
1378                 return 0;
1379         } else {
1380                 file = strrchr(ast_strdupa(dir), '/');
1381                 if (file)
1382                         *file++ = '\0';
1383                 else {
1384                         ast_debug (1, "Failed to procure file name from directory passed.\n");
1385                         return -1;
1386                 }
1387         }
1388
1389         /* check if someone is accessing this box right now... */
1390         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))) {
1391                 ast_log(AST_LOG_ERROR, "Voicemail state not found!\n");
1392                 return -1;
1393         }
1394         
1395         /* Greetings will never have a prepended message */
1396         *vms_p->introfn = '\0';
1397         
1398         ret = init_mailstream(vms_p, GREETINGS_FOLDER);
1399         if (!vms_p->mailstream) {
1400                 ast_log(AST_LOG_ERROR, "IMAP mailstream is NULL\n");
1401                 return -1;
1402         }
1403
1404         /*XXX Yuck, this could probably be done a lot better */
1405         for (i = 0; i < vms_p->mailstream->nmsgs; i++) {
1406                 mail_fetchstructure(vms_p->mailstream, i + 1, &body);
1407                 /* We have the body, now we extract the file name of the first attachment. */
1408                 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1409                         attachment = ast_strdupa(body->nested.part->next->body.parameter->value);
1410                 } else {
1411                         ast_log(AST_LOG_ERROR, "There is no file attached to this IMAP message.\n");
1412                         return -1;
1413                 }
1414                 filename = strsep(&attachment, ".");
1415                 if (!strcmp(filename, file)) {
1416                         ast_copy_string(vms_p->fn, dir, sizeof(vms_p->fn));
1417                         vms_p->msgArray[vms_p->curmsg] = i + 1;
1418                         save_body(body, vms_p, "2", attachment, 0);
1419                         return 0;
1420                 }
1421         }
1422
1423         return -1;
1424 }
1425
1426 static int imap_retrieve_file(const char *dir, const int msgnum, const char *mailbox, const char *context)
1427 {
1428         BODY *body;
1429         char *header_content;
1430         char *attachedfilefmt;
1431         char buf[80];
1432         struct vm_state *vms;
1433         char text_file[PATH_MAX];
1434         FILE *text_file_ptr;
1435         int res = 0;
1436         struct ast_vm_user *vmu;
1437
1438         if (!(vmu = find_user(NULL, context, mailbox))) {
1439                 ast_log(LOG_WARNING, "Couldn't find user with mailbox %s@%s\n", mailbox, context);
1440                 return -1;
1441         }
1442         
1443         if (msgnum < 0) {
1444                 if (imapgreetings) {
1445                         res = imap_retrieve_greeting(dir, msgnum, vmu);
1446                         goto exit;
1447                 } else {
1448                         res = 0;
1449                         goto exit;
1450                 }
1451         }
1452
1453         /* Before anything can happen, we need a vm_state so that we can
1454          * actually access the imap server through the vms->mailstream
1455          */
1456         if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1457                 /* This should not happen. If it does, then I guess we'd
1458                  * need to create the vm_state, extract which mailbox to
1459                  * open, and then set up the msgArray so that the correct
1460                  * IMAP message could be accessed. If I have seen correctly
1461                  * though, the vms should be obtainable from the vmstates list
1462                  * and should have its msgArray properly set up.
1463                  */
1464                 ast_log(LOG_ERROR, "Couldn't find a vm_state for mailbox %s!!! Oh no!\n", vmu->mailbox);
1465                 res = -1;
1466                 goto exit;
1467         }
1468         
1469         make_file(vms->fn, sizeof(vms->fn), dir, msgnum);
1470         snprintf(vms->introfn, sizeof(vms->introfn), "%sintro", vms->fn);
1471
1472         /* Don't try to retrieve a message from IMAP if it already is on the file system */
1473         if (ast_fileexists(vms->fn, NULL, NULL) > 0) {
1474                 res = 0;
1475                 goto exit;
1476         }
1477
1478         if (option_debug > 2)
1479                 ast_log (LOG_DEBUG,"Before mail_fetchheaders, curmsg is: %d, imap messages is %lu\n", msgnum, vms->msgArray[msgnum]);
1480         if (vms->msgArray[msgnum] == 0) {
1481                 ast_log (LOG_WARNING,"Trying to access unknown message\n");
1482                 res = -1;
1483                 goto exit;
1484         }
1485
1486         /* This will only work for new messages... */
1487         header_content = mail_fetchheader (vms->mailstream, vms->msgArray[msgnum]);
1488         /* empty string means no valid header */
1489         if (ast_strlen_zero(header_content)) {
1490                 ast_log (LOG_ERROR,"Could not fetch header for message number %ld\n",vms->msgArray[msgnum]);
1491                 res = -1;
1492                 goto exit;
1493         }
1494
1495         mail_fetchstructure (vms->mailstream,vms->msgArray[msgnum],&body);
1496         
1497         /* We have the body, now we extract the file name of the first attachment. */
1498         if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1499                 attachedfilefmt = ast_strdupa(body->nested.part->next->body.parameter->value);
1500         } else {
1501                 ast_log(LOG_ERROR, "There is no file attached to this IMAP message.\n");
1502                 res = -1;
1503                 goto exit;
1504         }
1505         
1506         /* Find the format of the attached file */
1507
1508         strsep(&attachedfilefmt, ".");
1509         if (!attachedfilefmt) {
1510                 ast_log(LOG_ERROR, "File format could not be obtained from IMAP message attachment\n");
1511                 res = -1;
1512                 goto exit;
1513         }
1514         
1515         save_body(body, vms, "2", attachedfilefmt, 0);
1516         if (save_body(body, vms, "3", attachedfilefmt, 1)) {
1517                 *vms->introfn = '\0';
1518         }
1519
1520         /* Get info from headers!! */
1521         snprintf(text_file, sizeof(text_file), "%s.%s", vms->fn, "txt");
1522
1523         if (!(text_file_ptr = fopen(text_file, "w"))) {
1524                 ast_log(LOG_WARNING, "Unable to open/create file %s: %s\n", text_file, strerror(errno));
1525         }
1526
1527         fprintf(text_file_ptr, "%s\n", "[message]");
1528
1529         get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf));
1530         fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, ""));
1531         get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf));
1532         fprintf(text_file_ptr, "<%s>\n", S_OR(buf, ""));
1533         get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf));
1534         fprintf(text_file_ptr, "context=%s\n", S_OR(buf, ""));
1535         get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf));
1536         fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, ""));
1537         get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf));
1538         fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, ""));
1539         get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf));
1540         fprintf(text_file_ptr, "category=%s\n", S_OR(buf, ""));
1541         fclose(text_file_ptr);
1542
1543 exit:
1544         free_user(vmu);
1545         return res;
1546 }
1547
1548 static int folder_int(const char *folder)
1549 {
1550         /*assume a NULL folder means INBOX*/
1551         if (!folder)
1552                 return 0;
1553 #ifdef IMAP_STORAGE
1554         if (!strcasecmp(folder, imapfolder))
1555 #else
1556         if (!strcasecmp(folder, "INBOX"))
1557 #endif
1558                 return 0;
1559         else if (!strcasecmp(folder, "Old"))
1560                 return 1;
1561         else if (!strcasecmp(folder, "Work"))
1562                 return 2;
1563         else if (!strcasecmp(folder, "Family"))
1564                 return 3;
1565         else if (!strcasecmp(folder, "Friends"))
1566                 return 4;
1567         else if (!strcasecmp(folder, "Cust1"))
1568                 return 5;
1569         else if (!strcasecmp(folder, "Cust2"))
1570                 return 6;
1571         else if (!strcasecmp(folder, "Cust3"))
1572                 return 7;
1573         else if (!strcasecmp(folder, "Cust4"))
1574                 return 8;
1575         else if (!strcasecmp(folder, "Cust5"))
1576                 return 9;
1577         else /*assume they meant INBOX if folder is not found otherwise*/
1578                 return 0;
1579 }
1580
1581 /*!
1582  * \brief Gets the number of messages that exist in a mailbox folder.
1583  * \param context
1584  * \param mailbox
1585  * \param folder
1586  * 
1587  * This method is used when IMAP backend is used.
1588  * \return The number of messages in this mailbox folder (zero or more).
1589  */
1590 static int messagecount(const char *context, const char *mailbox, const char *folder)
1591 {
1592         SEARCHPGM *pgm;
1593         SEARCHHEADER *hdr;
1594
1595         struct ast_vm_user *vmu, vmus;
1596         struct vm_state *vms_p;
1597         int ret = 0;
1598         int fold = folder_int(folder);
1599         int urgent = 0;
1600         
1601         if (ast_strlen_zero(mailbox))
1602                 return 0;
1603
1604         /* We have to get the user before we can open the stream! */
1605         vmu = find_user(&vmus, context, mailbox);
1606         if (!vmu) {
1607                 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailbox, context);
1608                 return -1;
1609         } else {
1610                 /* No IMAP account available */
1611                 if (vmu->imapuser[0] == '\0') {
1612                         ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1613                         return -1;
1614                 }
1615         }
1616         
1617         /* No IMAP account available */
1618         if (vmu->imapuser[0] == '\0') {
1619                 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1620                 free_user(vmu);
1621                 return -1;
1622         }
1623
1624         /* check if someone is accessing this box right now... */
1625         vms_p = get_vm_state_by_imapuser(vmu->imapuser,1);
1626         if (!vms_p) {
1627                 vms_p = get_vm_state_by_mailbox(mailbox, context, 1);
1628         }
1629         if (vms_p) {
1630                 ast_debug(3, "Returning before search - user is logged in\n");
1631                 if (fold == 0) { /* INBOX */
1632                         return vms_p->newmessages;
1633                 }
1634                 if (fold == 1) { /* Old messages */
1635                         return vms_p->oldmessages;
1636                 }
1637                 if (fold == 11) {/*Urgent messages*/
1638                         return vms_p->urgentmessages;
1639                 }
1640         }
1641
1642         /* add one if not there... */
1643         vms_p = get_vm_state_by_imapuser(vmu->imapuser,0);
1644         if (!vms_p) {
1645                 vms_p = get_vm_state_by_mailbox(mailbox, context, 0);
1646         }
1647
1648         /* If URGENT, then look at INBOX */
1649         if (fold == 11) {
1650                 fold = NEW_FOLDER;
1651                 urgent = 1;
1652         }
1653
1654         if (!vms_p) {
1655                 ast_debug(3,"Adding new vmstate for %s\n",vmu->imapuser);
1656                 if (!(vms_p = ast_calloc(1, sizeof(*vms_p)))) {
1657                         return -1;
1658                 }
1659                 ast_copy_string(vms_p->imapuser,vmu->imapuser, sizeof(vms_p->imapuser));
1660                 ast_copy_string(vms_p->username, mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
1661                 vms_p->mailstream = NIL; /* save for access from interactive entry point */
1662                 ast_debug(3, "Copied %s to %s\n",vmu->imapuser,vms_p->imapuser);
1663                 vms_p->updated = 1;
1664                 ast_copy_string(vms_p->curbox, mbox(fold), sizeof(vms_p->curbox));
1665                 init_vm_state(vms_p);
1666                 vmstate_insert(vms_p);
1667         }
1668         ret = init_mailstream(vms_p, fold);
1669         if (!vms_p->mailstream) {
1670                 ast_log(AST_LOG_ERROR, "Houston we have a problem - IMAP mailstream is NULL\n");
1671                 return -1;
1672         }
1673         if (ret == 0) {
1674                 pgm = mail_newsearchpgm ();
1675                 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)mailbox);
1676                 pgm->header = hdr;
1677                 if (fold != 1) {
1678                         pgm->unseen = 1;
1679                         pgm->seen = 0;
1680                 }
1681                 /* In the special case where fold is 1 (old messages) we have to do things a bit
1682                  * differently. Old messages are stored in the INBOX but are marked as "seen"
1683                  */
1684                 else {
1685                         pgm->unseen = 0;
1686                         pgm->seen = 1;
1687                 }
1688                 /* look for urgent messages */
1689                 if (urgent) {
1690                         pgm->flagged = 1;
1691                         pgm->unflagged = 0;
1692                 }
1693                 pgm->undeleted = 1;
1694                 pgm->deleted = 0;
1695
1696                 vms_p->vmArrayIndex = 0;
1697                 mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
1698                 if (fold == 0 && urgent == 0)
1699                         vms_p->newmessages = vms_p->vmArrayIndex;
1700                 if (fold == 1)
1701                         vms_p->oldmessages = vms_p->vmArrayIndex;
1702                 if (fold == 0 && urgent == 1)
1703                         vms_p->urgentmessages = vms_p->vmArrayIndex;
1704                 /*Freeing the searchpgm also frees the searchhdr*/
1705                 mail_free_searchpgm(&pgm);
1706                 vms_p->updated = 0;
1707                 return vms_p->vmArrayIndex;
1708         } else {  
1709                 mail_ping(vms_p->mailstream);
1710         }
1711         return 0;
1712 }
1713
1714 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)
1715 {
1716         char *myserveremail = serveremail;
1717         char fn[PATH_MAX];
1718         char introfn[PATH_MAX];
1719         char mailbox[256];
1720         char *stringp;
1721         FILE *p=NULL;
1722         char tmp[80] = "/tmp/astmail-XXXXXX";
1723         long len;
1724         void *buf;
1725         int tempcopy = 0;
1726         STRING str;
1727         int ret; /* for better error checking */
1728         char *imap_flags = NIL;
1729
1730         /* Set urgent flag for IMAP message */
1731         if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) {
1732                 ast_debug(3, "Setting message flag \\\\FLAGGED.\n");
1733                 imap_flags="\\FLAGGED";
1734         }
1735         
1736         /* Attach only the first format */
1737         fmt = ast_strdupa(fmt);
1738         stringp = fmt;
1739         strsep(&stringp, "|");
1740
1741         if (!ast_strlen_zero(vmu->serveremail))
1742                 myserveremail = vmu->serveremail;
1743
1744         if (msgnum > -1)
1745                 make_file(fn, sizeof(fn), dir, msgnum);
1746         else
1747                 ast_copy_string (fn, dir, sizeof(fn));
1748
1749         snprintf(introfn, sizeof(introfn), "%sintro", fn);
1750         if (ast_fileexists(introfn, NULL, NULL) <= 0) {
1751                 *introfn = '\0';
1752         }
1753         
1754         if (ast_strlen_zero(vmu->email)) {
1755                 /* We need the vmu->email to be set when we call make_email_file, but
1756                  * if we keep it set, a duplicate e-mail will be created. So at the end
1757                  * of this function, we will revert back to an empty string if tempcopy
1758                  * is 1.
1759                  */
1760                 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
1761                 tempcopy = 1;
1762         }
1763
1764         if (!strcmp(fmt, "wav49"))
1765                 fmt = "WAV";
1766         ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
1767
1768         /* Make a temporary file instead of piping directly to sendmail, in case the mail
1769            command hangs. */
1770         if (!(p = vm_mkftemp(tmp))) {
1771                 ast_log(AST_LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
1772                 if (tempcopy)
1773                         *(vmu->email) = '\0';
1774                 return -1;
1775         }
1776
1777         if (msgnum < 0 && imapgreetings) {
1778                 if ((ret = init_mailstream(vms, GREETINGS_FOLDER))) {
1779                         ast_log(AST_LOG_WARNING, "Unable to open mailstream.\n");
1780                         return -1;
1781                 }
1782                 imap_delete_old_greeting(fn, vms);
1783         }
1784         
1785         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);
1786         /* read mail file to memory */          
1787         len = ftell(p);
1788         rewind(p);
1789         if (!(buf = ast_malloc(len + 1))) {
1790                 ast_log(AST_LOG_ERROR, "Can't allocate %ld bytes to read message\n", len + 1);
1791                 fclose(p);
1792                 if (tempcopy)
1793                         *(vmu->email) = '\0';
1794                 return -1;
1795         }
1796         fread(buf, len, 1, p);
1797         ((char *)buf)[len] = '\0';
1798         INIT(&str, mail_string, buf, len);
1799         ret = init_mailstream(vms, NEW_FOLDER);
1800         if (ret == 0) {
1801                 imap_mailbox_name(mailbox, sizeof(mailbox), vms, NEW_FOLDER, 1);
1802                 if(!mail_append_full(vms->mailstream, mailbox, imap_flags, NIL, &str))
1803                         ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
1804                 fclose(p);
1805                 unlink(tmp);
1806                 ast_free(buf);
1807         } else {
1808                 ast_log(LOG_ERROR, "Could not initialize mailstream for %s\n",mailbox);
1809                 fclose(p);
1810                 unlink(tmp);
1811                 ast_free(buf);
1812                 return -1;
1813         }
1814         ast_debug(3, "%s stored\n", fn);
1815         
1816         if (tempcopy)
1817                 *(vmu->email) = '\0';
1818         
1819         return 0;
1820
1821 }
1822
1823 /*!
1824  * \brief Gets the number of messages that exist in the inbox folder.
1825  * \param mailbox_context
1826  * \param newmsgs The variable that is updated with the count of new messages within this inbox.
1827  * \param oldmsgs The variable that is updated with the count of old messages within this inbox.
1828  * \param urgentmsgs The variable that is updated with the count of urgent messages within this inbox.
1829  * 
1830  * This method is used when IMAP backend is used.
1831  * Simultaneously determines the count of new,old, and urgent messages. The total messages would then be the sum of these three.
1832  *
1833  * \return zero on success, -1 on error.
1834  */
1835
1836 static int inboxcount2(const char *mailbox_context, int *urgentmsgs, int *newmsgs, int *oldmsgs)
1837 {
1838         char tmp[PATH_MAX] = "";
1839         char *mailboxnc;
1840         char *context;
1841         char *mb;
1842         char *cur;
1843         if (newmsgs)
1844                 *newmsgs = 0;
1845         if (oldmsgs)
1846                 *oldmsgs = 0;
1847         if (urgentmsgs)
1848                 *urgentmsgs = 0;
1849
1850         ast_debug(3,"Mailbox is set to %s\n",mailbox_context);
1851         /* If no mailbox, return immediately */
1852         if (ast_strlen_zero(mailbox_context))
1853                 return 0;
1854         
1855         ast_copy_string(tmp, mailbox_context, sizeof(tmp));
1856         context = strchr(tmp, '@');
1857         if (strchr(mailbox_context, ',')) {
1858                 int tmpnew, tmpold, tmpurgent;
1859                 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
1860                 mb = tmp;
1861                 while ((cur = strsep(&mb, ", "))) {
1862                         if (!ast_strlen_zero(cur)) {
1863                                 if (inboxcount2(cur, urgentmsgs ? &tmpurgent : NULL, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
1864                                         return -1;
1865                                 else {
1866                                         if (newmsgs)
1867                                                 *newmsgs += tmpnew; 
1868                                         if (oldmsgs)
1869                                                 *oldmsgs += tmpold;
1870                                         if (urgentmsgs)
1871                                                 *urgentmsgs += tmpurgent;
1872                                 }
1873                         }
1874                 }
1875                 return 0;
1876         }
1877         if (context) {
1878                 *context = '\0';
1879                 mailboxnc = tmp;
1880                 context++;
1881         } else {
1882                 context = "default";
1883                 mailboxnc = (char *)mailbox_context;
1884         }
1885         if (newmsgs) {
1886                 if ((*newmsgs = messagecount(context, mailboxnc, imapfolder)) < 0)
1887                         return -1;
1888         }
1889         if (oldmsgs) {
1890                 if ((*oldmsgs = messagecount(context, mailboxnc, "Old")) < 0)
1891                         return -1;
1892         }
1893         if (urgentmsgs) {
1894                 if((*urgentmsgs = messagecount(context, mailboxnc, "Urgent")) < 0)
1895                         return -1;
1896         }
1897         return 0;
1898 }
1899
1900 static int inboxcount(const char *mailbox_context, int *newmsgs, int *oldmsgs)
1901 {
1902         return inboxcount2(mailbox_context, NULL, newmsgs, oldmsgs);
1903 }
1904
1905 /** 
1906  * \brief Determines if the given folder has messages.
1907  * \param mailbox The @ delimited string for user@context. If no context is found, uses 'default' for the context.
1908  * \param folder the folder to look in
1909  *
1910  * This function is used when the mailbox is stored in an IMAP back end.
1911  * This invokes the messagecount(). Here we are interested in the presence of messages (> 0) only, not the actual count.
1912  * \return 1 if the folder has one or more messages. zero otherwise.
1913  */
1914
1915 static int has_voicemail(const char *mailbox, const char *folder)
1916 {
1917         char tmp[256], *tmp2, *box, *context;
1918         ast_copy_string(tmp, mailbox, sizeof(tmp));
1919         tmp2 = tmp;
1920         if (strchr(tmp2, ',')) {
1921                 while ((box = strsep(&tmp2, ","))) {
1922                         if (!ast_strlen_zero(box)) {
1923                                 if (has_voicemail(box, folder))
1924                                         return 1;
1925                         }
1926                 }
1927         }
1928         if ((context= strchr(tmp, '@')))
1929                 *context++ = '\0';
1930         else
1931                 context = "default";
1932         return messagecount(context, tmp, folder) ? 1 : 0;
1933 }
1934
1935 /*!
1936  * \brief Copies a message from one mailbox to another.
1937  * \param chan
1938  * \param vmu
1939  * \param imbox
1940  * \param msgnum
1941  * \param duration
1942  * \param recip
1943  * \param fmt
1944  * \param dir
1945  *
1946  * This works with IMAP storage based mailboxes.
1947  *
1948  * \return zero on success, -1 on error.
1949  */
1950 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)
1951 {
1952         struct vm_state *sendvms = NULL, *destvms = NULL;
1953         char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
1954         if (msgnum >= recip->maxmsg) {
1955                 ast_log(LOG_WARNING, "Unable to copy mail, mailbox %s is full\n", recip->mailbox);
1956                 return -1;
1957         }
1958         if (!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 0))) {
1959                 ast_log(LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
1960                 return -1;
1961         }
1962         if (!(destvms = get_vm_state_by_imapuser(recip->imapuser, 0))) {
1963                 ast_log(LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
1964                 return -1;
1965         }
1966         snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
1967         if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(imbox)) == T))
1968                 return 0;
1969         ast_log(LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
1970         return -1;
1971 }
1972
1973 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int use_folder)
1974 {
1975         char tmp[256], *t = tmp;
1976         size_t left = sizeof(tmp);
1977         
1978         if (box == OLD_FOLDER) {
1979                 ast_copy_string(vms->curbox, mbox(NEW_FOLDER), sizeof(vms->curbox));
1980         } else {
1981                 ast_copy_string(vms->curbox, mbox(box), sizeof(vms->curbox));
1982         }
1983
1984         if (box == NEW_FOLDER) {
1985                 ast_copy_string(vms->vmbox, "vm-INBOX", sizeof(vms->vmbox));
1986         } else {
1987                 snprintf(vms->vmbox, sizeof(vms->vmbox), "vm-%s", mbox(box));
1988         }
1989
1990         /* Build up server information */
1991         ast_build_string(&t, &left, "{%s:%s/imap", imapserver, imapport);
1992
1993         /* Add authentication user if present */
1994         if (!ast_strlen_zero(authuser))
1995                 ast_build_string(&t, &left, "/authuser=%s", authuser);
1996
1997         /* Add flags if present */
1998         if (!ast_strlen_zero(imapflags))
1999                 ast_build_string(&t, &left, "/%s", imapflags);
2000
2001         /* End with username */
2002         ast_build_string(&t, &left, "/user=%s}", vms->imapuser);
2003         if (box == NEW_FOLDER || box == OLD_FOLDER)
2004                 snprintf(spec, len, "%s%s", tmp, use_folder? imapfolder: "INBOX");
2005         else if (box == GREETINGS_FOLDER)
2006                 snprintf(spec, len, "%s%s", tmp, greetingfolder);
2007         else {  /* Other folders such as Friends, Family, etc... */
2008                 if (!ast_strlen_zero(imapparentfolder)) {
2009                         /* imapparentfolder would typically be set to INBOX */
2010                         snprintf(spec, len, "%s%s%c%s", tmp, imapparentfolder, delimiter, mbox(box));
2011                 } else {
2012                         snprintf(spec, len, "%s%s", tmp, mbox(box));
2013                 }
2014         }
2015 }
2016
2017 static int init_mailstream(struct vm_state *vms, int box)
2018 {
2019         MAILSTREAM *stream = NIL;
2020         long debug;
2021         char tmp[256];
2022         
2023         if (!vms) {
2024                 ast_log (LOG_ERROR,"vm_state is NULL!\n");
2025                 return -1;
2026         }
2027         if (option_debug > 2)
2028                 ast_log (LOG_DEBUG,"vm_state user is:%s\n",vms->imapuser);
2029         if (vms->mailstream == NIL || !vms->mailstream) {
2030                 if (option_debug)
2031                         ast_log (LOG_DEBUG,"mailstream not set.\n");
2032         } else {
2033                 stream = vms->mailstream;
2034         }
2035         /* debug = T;  user wants protocol telemetry? */
2036         debug = NIL;  /* NO protocol telemetry? */
2037
2038         if (delimiter == '\0') {                /* did not probe the server yet */
2039                 char *cp;
2040 #ifdef USE_SYSTEM_IMAP
2041 #include <imap/linkage.c>
2042 #elif defined(USE_SYSTEM_CCLIENT)
2043 #include <c-client/linkage.c>
2044 #else
2045 #include "linkage.c"
2046 #endif
2047                 /* Connect to INBOX first to get folders delimiter */
2048                 imap_mailbox_name(tmp, sizeof(tmp), vms, 0, 1);
2049                 ast_mutex_lock(&vms->lock);
2050                 stream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2051                 ast_mutex_unlock(&vms->lock);
2052                 if (stream == NIL) {
2053                         ast_log (LOG_ERROR, "Can't connect to imap server %s\n", tmp);
2054                         return -1;
2055                 }
2056                 get_mailbox_delimiter(stream);
2057                 /* update delimiter in imapfolder */
2058                 for (cp = imapfolder; *cp; cp++)
2059                         if (*cp == '/')
2060                                 *cp = delimiter;
2061         }
2062         /* Now connect to the target folder */
2063         imap_mailbox_name(tmp, sizeof(tmp), vms, box, 1);
2064         if (option_debug > 2)
2065                 ast_log (LOG_DEBUG,"Before mail_open, server: %s, box:%d\n", tmp, box);
2066         ast_mutex_lock(&vms->lock);
2067         vms->mailstream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2068         ast_mutex_unlock(&vms->lock);
2069         if (vms->mailstream == NIL) {
2070                 return -1;
2071         } else {
2072                 return 0;
2073         }
2074 }
2075
2076 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box)
2077 {
2078         SEARCHPGM *pgm;
2079         SEARCHHEADER *hdr;
2080         int ret, urgent = 0;
2081
2082         /* If Urgent, then look at INBOX */
2083         if (box == 11) {
2084                 box = NEW_FOLDER;
2085                 urgent = 1;
2086         }
2087
2088         ast_copy_string(vms->imapuser,vmu->imapuser, sizeof(vms->imapuser));
2089         ast_debug(3,"Before init_mailstream, user is %s\n",vmu->imapuser);
2090
2091         if ((ret = init_mailstream(vms, box)) || !vms->mailstream) {
2092                 ast_log(AST_LOG_ERROR, "Could not initialize mailstream\n");
2093                 return -1;
2094         }
2095         
2096         create_dirpath(vms->curdir, sizeof(vms->curdir), vmu->context, vms->username, vms->curbox);
2097         
2098         /* Check Quota */
2099         if  (box == 0)  {
2100                 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mbox(box));
2101                 check_quota(vms,(char *)mbox(box));
2102         }
2103
2104         pgm = mail_newsearchpgm();
2105
2106         /* Check IMAP folder for Asterisk messages only... */
2107         hdr = mail_newsearchheader("X-Asterisk-VM-Extension", vmu->mailbox);
2108         pgm->header = hdr;
2109         pgm->deleted = 0;
2110         pgm->undeleted = 1;
2111
2112         /* if box = NEW_FOLDER, check for new, if box = OLD_FOLDER, check for read */
2113         if (box == NEW_FOLDER && urgent == 1) {
2114                 pgm->unseen = 1;
2115                 pgm->seen = 0;
2116                 pgm->flagged = 1;
2117                 pgm->unflagged = 0;
2118         } else if (box == NEW_FOLDER && urgent == 0) {
2119                 pgm->unseen = 1;
2120                 pgm->seen = 0;
2121                 pgm->flagged = 0;
2122                 pgm->unflagged = 1;
2123         } else if (box == OLD_FOLDER) {
2124                 pgm->seen = 1;
2125                 pgm->unseen = 0;
2126         }
2127
2128         ast_debug(3,"Before mail_search_full, user is %s\n",vmu->imapuser);
2129
2130         vms->vmArrayIndex = 0;
2131         mail_search_full (vms->mailstream, NULL, pgm, NIL);
2132         vms->lastmsg = vms->vmArrayIndex - 1;
2133         mail_free_searchpgm(&pgm);
2134
2135         return 0;
2136 }
2137
2138 static void write_file(char *filename, char *buffer, unsigned long len)
2139 {
2140         FILE *output;
2141
2142         output = fopen (filename, "w");
2143         fwrite (buffer, len, 1, output);
2144         fclose (output);
2145 }
2146
2147 static void update_messages_by_imapuser(const char *user, unsigned long number)
2148 {
2149         struct vmstate *vlist = NULL;
2150
2151         AST_LIST_LOCK(&vmstates);
2152         AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2153                 if (!vlist->vms) {
2154                         ast_debug(3, "error: vms is NULL for %s\n", user);
2155                         continue;
2156                 }
2157                 if (!vlist->vms->imapuser) {
2158                         ast_debug(3, "error: imapuser is NULL for %s\n", user);
2159                         continue;
2160                 }
2161                 ast_debug(3, "saving mailbox message number %lu as message %d. Interactive set to %d\n", number, vlist->vms->vmArrayIndex, vlist->vms->interactive);
2162                 vlist->vms->msgArray[vlist->vms->vmArrayIndex++] = number;
2163         }
2164         AST_LIST_UNLOCK(&vmstates);
2165 }
2166
2167 void mm_searched(MAILSTREAM *stream, unsigned long number)
2168 {
2169         char *mailbox = stream->mailbox, buf[1024] = "", *user;
2170
2171         if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))))
2172                 return;
2173
2174         update_messages_by_imapuser(user, number);
2175 }
2176
2177 static struct ast_vm_user *find_user_realtime_imapuser(const char *imapuser)
2178 {
2179         struct ast_variable *var;
2180         struct ast_vm_user *vmu;
2181
2182         vmu = ast_calloc(1, sizeof *vmu);
2183         if (!vmu)
2184                 return NULL;
2185         ast_set_flag(vmu, VM_ALLOCED);
2186         populate_defaults(vmu);
2187
2188         var = ast_load_realtime("voicemail", "imapuser", imapuser, NULL);
2189         if (var) {
2190                 apply_options_full(vmu, var);
2191                 ast_variables_destroy(var);
2192                 return vmu;
2193         } else {
2194                 free(vmu);
2195                 return NULL;
2196         }
2197 }
2198
2199 /* Interfaces to C-client */
2200
2201 void mm_exists(MAILSTREAM * stream, unsigned long number)
2202 {
2203         /* mail_ping will callback here if new mail! */
2204         ast_debug(4, "Entering EXISTS callback for message %ld\n", number);
2205         if (number == 0) return;
2206         set_update(stream);
2207 }
2208
2209
2210 void mm_expunged(MAILSTREAM * stream, unsigned long number)
2211 {
2212         /* mail_ping will callback here if expunged mail! */
2213         ast_debug(4, "Entering EXPUNGE callback for message %ld\n", number);
2214         if (number == 0) return;
2215         set_update(stream);
2216 }
2217
2218
2219 void mm_flags(MAILSTREAM * stream, unsigned long number)
2220 {
2221         /* mail_ping will callback here if read mail! */
2222         ast_debug(4, "Entering FLAGS callback for message %ld\n", number);
2223         if (number == 0) return;
2224         set_update(stream);
2225 }
2226
2227
2228 void mm_notify(MAILSTREAM * stream, char *string, long errflg)
2229 {
2230         ast_debug(5, "Entering NOTIFY callback, errflag is %ld, string is %s\n", errflg, string);
2231         mm_log (string, errflg);
2232 }
2233
2234
2235 void mm_list(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2236 {
2237         if (delimiter == '\0') {
2238                 delimiter = delim;
2239         }
2240
2241         ast_debug(5, "Delimiter set to %c and mailbox %s\n",delim, mailbox);
2242         if (attributes & LATT_NOINFERIORS)
2243                 ast_debug(5, "no inferiors\n");
2244         if (attributes & LATT_NOSELECT)
2245                 ast_debug(5, "no select\n");
2246         if (attributes & LATT_MARKED)
2247                 ast_debug(5, "marked\n");
2248         if (attributes & LATT_UNMARKED)
2249                 ast_debug(5, "unmarked\n");
2250 }
2251
2252
2253 void mm_lsub(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2254 {
2255         ast_debug(5, "Delimiter set to %c and mailbox %s\n",delim, mailbox);
2256         if (attributes & LATT_NOINFERIORS)
2257                 ast_debug(5, "no inferiors\n");
2258         if (attributes & LATT_NOSELECT)
2259                 ast_debug(5, "no select\n");
2260         if (attributes & LATT_MARKED)
2261                 ast_debug(5, "marked\n");
2262         if (attributes & LATT_UNMARKED)
2263                 ast_debug(5, "unmarked\n");
2264 }
2265
2266
2267 void mm_status(MAILSTREAM * stream, char *mailbox, MAILSTATUS * status)
2268 {
2269         ast_log(AST_LOG_NOTICE, " Mailbox %s", mailbox);
2270         if (status->flags & SA_MESSAGES)
2271                 ast_log(AST_LOG_NOTICE, ", %lu messages", status->messages);
2272         if (status->flags & SA_RECENT)
2273                 ast_log(AST_LOG_NOTICE, ", %lu recent", status->recent);
2274         if (status->flags & SA_UNSEEN)
2275                 ast_log(AST_LOG_NOTICE, ", %lu unseen", status->unseen);
2276         if (status->flags & SA_UIDVALIDITY)
2277                 ast_log(AST_LOG_NOTICE, ", %lu UID validity", status->uidvalidity);
2278         if (status->flags & SA_UIDNEXT)
2279                 ast_log(AST_LOG_NOTICE, ", %lu next UID", status->uidnext);
2280         ast_log(AST_LOG_NOTICE, "\n");
2281 }
2282
2283
2284 void mm_log(char *string, long errflg)
2285 {
2286         switch ((short) errflg) {
2287                 case NIL:
2288                         ast_debug(1,"IMAP Info: %s\n", string);
2289                         break;
2290                 case PARSE:
2291                 case WARN:
2292                         ast_log(AST_LOG_WARNING, "IMAP Warning: %s\n", string);
2293                         break;
2294                 case ERROR:
2295                         ast_log(AST_LOG_ERROR, "IMAP Error: %s\n", string);
2296                         break;
2297         }
2298 }
2299
2300
2301 void mm_dlog(char *string)
2302 {
2303         ast_log(AST_LOG_NOTICE, "%s\n", string);
2304 }
2305
2306
2307 void mm_login(NETMBX * mb, char *user, char *pwd, long trial)
2308 {
2309         struct ast_vm_user *vmu;
2310
2311         ast_debug(4, "Entering callback mm_login\n");
2312
2313         ast_copy_string(user, mb->user, MAILTMPLEN);
2314
2315         /* We should only do this when necessary */
2316         if (!ast_strlen_zero(authpassword)) {
2317                 ast_copy_string(pwd, authpassword, MAILTMPLEN);
2318         } else {
2319                 AST_LIST_TRAVERSE(&users, vmu, list) {
2320                         if (!strcasecmp(mb->user, vmu->imapuser)) {
2321                                 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2322                                 break;
2323                         }
2324                 }
2325                 if (!vmu) {
2326                         if ((vmu = find_user_realtime_imapuser(mb->user))) {
2327                                 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2328                                 free_user(vmu);
2329                         }
2330                 }
2331         }
2332 }
2333
2334
2335 void mm_critical(MAILSTREAM * stream)
2336 {
2337 }
2338
2339
2340 void mm_nocritical(MAILSTREAM * stream)
2341 {
2342 }
2343
2344
2345 long mm_diskerror(MAILSTREAM * stream, long errcode, long serious)
2346 {
2347         kill (getpid (), SIGSTOP);
2348         return NIL;
2349 }
2350
2351
2352 void mm_fatal(char *string)
2353 {
2354         ast_log(AST_LOG_ERROR, "IMAP access FATAL error: %s\n", string);
2355 }
2356
2357 /* C-client callback to handle quota */
2358 static void mm_parsequota(MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
2359 {
2360         struct vm_state *vms;
2361         char *mailbox = stream->mailbox, *user;
2362         char buf[1024] = "";
2363         unsigned long usage = 0, limit = 0;
2364         
2365         while (pquota) {
2366                 usage = pquota->usage;
2367                 limit = pquota->limit;
2368                 pquota = pquota->next;
2369         }
2370         
2371         if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || !(vms = get_vm_state_by_imapuser(user, 2))) {
2372                 ast_log(AST_LOG_ERROR, "No state found.\n");
2373                 return;
2374         }
2375
2376         ast_debug(3, "User %s usage is %lu, limit is %lu\n", user, usage, limit);
2377
2378         vms->quota_usage = usage;
2379         vms->quota_limit = limit;
2380 }
2381
2382 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len)
2383 {
2384         char *start, *eol_pnt;
2385         int taglen;
2386
2387         if (ast_strlen_zero(header) || ast_strlen_zero(tag))
2388                 return NULL;
2389
2390         taglen = strlen(tag) + 1;
2391         if (taglen < 1)
2392                 return NULL;
2393
2394         if (!(start = strstr(header, tag)))
2395                 return NULL;
2396
2397         /* Since we can be called multiple times we should clear our buffer */
2398         memset(buf, 0, len);
2399
2400         ast_copy_string(buf, start+taglen, len);
2401         if ((eol_pnt = strchr(buf,'\r')) || (eol_pnt = strchr(buf,'\n')))
2402                 *eol_pnt = '\0';
2403         return buf;
2404 }
2405
2406 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len)
2407 {
2408         char *start, *quote, *eol_pnt;
2409
2410         if (ast_strlen_zero(mailbox))
2411                 return NULL;
2412
2413         if (!(start = strstr(mailbox, "/user=")))
2414                 return NULL;
2415
2416         ast_copy_string(buf, start+6, len);
2417
2418         if (!(quote = strchr(buf, '\"'))) {
2419                 if (!(eol_pnt = strchr(buf, '/')))
2420                         eol_pnt = strchr(buf,'}');
2421                 *eol_pnt = '\0';
2422                 return buf;
2423         } else {
2424                 eol_pnt = strchr(buf+1,'\"');
2425                 *eol_pnt = '\0';
2426                 return buf+1;
2427         }
2428 }
2429
2430 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu)
2431 {
2432         struct vm_state *vms_p;
2433
2434         if (option_debug > 4)
2435                 ast_log(AST_LOG_DEBUG,"Adding new vmstate for %s\n",vmu->imapuser);
2436         if (!(vms_p = ast_calloc(1, sizeof(*vms_p))))
2437                 return NULL;
2438         ast_copy_string(vms_p->imapuser, vmu->imapuser, sizeof(vms_p->imapuser));
2439         ast_copy_string(vms_p->username, vmu->mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
2440         ast_copy_string(vms_p->context, vmu->context, sizeof(vms_p->context));
2441         vms_p->mailstream = NIL; /* save for access from interactive entry point */
2442         if (option_debug > 4)
2443                 ast_log(AST_LOG_DEBUG,"Copied %s to %s\n",vmu->imapuser,vms_p->imapuser);
2444         vms_p->updated = 1;
2445         /* set mailbox to INBOX! */
2446         ast_copy_string(vms_p->curbox, mbox(0), sizeof(vms_p->curbox));
2447         init_vm_state(vms_p);
2448         vmstate_insert(vms_p);
2449         return vms_p;
2450 }
2451
2452 static struct vm_state *get_vm_state_by_imapuser(char *user, int interactive)
2453 {
2454         struct vmstate *vlist = NULL;
2455
2456         AST_LIST_LOCK(&vmstates);
2457         AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2458                 if (!vlist->vms) {
2459                         ast_debug(3, "error: vms is NULL for %s\n", user);
2460                         continue;
2461                 }
2462                 if (!vlist->vms->imapuser) {
2463                         ast_debug(3, "error: imapuser is NULL for %s\n", user);
2464                         continue;
2465                 }
2466
2467                 if (!strcmp(vlist->vms->imapuser, user) && (interactive == 2 || vlist->vms->interactive == interactive)) {
2468                         AST_LIST_UNLOCK(&vmstates);
2469                         return vlist->vms;
2470                 }
2471         }
2472         AST_LIST_UNLOCK(&vmstates);
2473
2474         ast_debug(3, "%s not found in vmstates\n", user);
2475
2476         return NULL;
2477 }
2478
2479 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive)
2480 {
2481
2482         struct vmstate *vlist = NULL;
2483         const char *local_context = S_OR(context, "default");
2484
2485         AST_LIST_LOCK(&vmstates);
2486         AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2487                 if (!vlist->vms) {
2488                         ast_debug(3, "error: vms is NULL for %s\n", mailbox);
2489                         continue;
2490                 }
2491                 if (!vlist->vms->username || !vlist->vms->context) {
2492                         ast_debug(3, "error: username is NULL for %s\n", mailbox);
2493                         continue;
2494                 }
2495
2496                 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);
2497                 
2498                 if (!strcmp(vlist->vms->username,mailbox) && !strcmp(vlist->vms->context, local_context) && vlist->vms->interactive == interactive) {
2499                         ast_debug(3, "Found it!\n");
2500                         AST_LIST_UNLOCK(&vmstates);
2501                         return vlist->vms;
2502                 }
2503         }
2504         AST_LIST_UNLOCK(&vmstates);
2505
2506         ast_debug(3, "%s not found in vmstates\n", mailbox);
2507
2508         return NULL;
2509 }
2510
2511 static void vmstate_insert(struct vm_state *vms) 
2512 {
2513         struct vmstate *v;
2514         struct vm_state *altvms;
2515
2516         /* If interactive, it probably already exists, and we should
2517            use the one we already have since it is more up to date.
2518            We can compare the username to find the duplicate */
2519         if (vms->interactive == 1) {
2520                 altvms = get_vm_state_by_mailbox(vms->username, vms->context, 0);
2521                 if (altvms) {   
2522                         ast_debug(3, "Duplicate mailbox %s, copying message info...\n",vms->username);
2523                         vms->newmessages = altvms->newmessages;
2524                         vms->oldmessages = altvms->oldmessages;
2525                         vms->vmArrayIndex = altvms->vmArrayIndex;
2526                         vms->lastmsg = altvms->lastmsg;
2527                         vms->curmsg = altvms->curmsg;
2528                         /* get a pointer to the persistent store */
2529                         vms->persist_vms = altvms;
2530                         /* Reuse the mailstream? */
2531                         vms->mailstream = altvms->mailstream;
2532                         /* vms->mailstream = NIL; */
2533                 }
2534         }
2535
2536         if (!(v = ast_calloc(1, sizeof(*v))))
2537                 return;
2538         
2539         v->vms = vms;
2540
2541         ast_debug(3, "Inserting vm_state for user:%s, mailbox %s\n",vms->imapuser,vms->username);
2542
2543         AST_LIST_LOCK(&vmstates);
2544         AST_LIST_INSERT_TAIL(&vmstates, v, list);
2545         AST_LIST_UNLOCK(&vmstates);
2546 }
2547
2548 static void vmstate_delete(struct vm_state *vms) 
2549 {
2550         struct vmstate *vc = NULL;
2551         struct vm_state *altvms = NULL;
2552
2553         /* If interactive, we should copy pertinent info
2554            back to the persistent state (to make update immediate) */
2555         if (vms->interactive == 1 && (altvms = vms->persist_vms)) {
2556                 ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
2557                 altvms->newmessages = vms->newmessages;
2558                 altvms->oldmessages = vms->oldmessages;
2559                 altvms->updated = 1;
2560         }
2561         
2562         ast_debug(3, "Removing vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2563         
2564         AST_LIST_LOCK(&vmstates);
2565         AST_LIST_TRAVERSE_SAFE_BEGIN(&vmstates, vc, list) {
2566                 if (vc->vms == vms) {
2567                         AST_LIST_REMOVE_CURRENT(list);
2568                         break;
2569                 }
2570         }
2571         AST_LIST_TRAVERSE_SAFE_END
2572         AST_LIST_UNLOCK(&vmstates);
2573         
2574         if (vc) {
2575                 ast_mutex_destroy(&vc->vms->lock);
2576                 ast_free(vc);
2577         }
2578         else
2579                 ast_log(AST_LOG_ERROR, "No vmstate found for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2580 }
2581
2582 static void set_update(MAILSTREAM * stream) 
2583 {
2584         struct vm_state *vms;
2585         char *mailbox = stream->mailbox, *user;
2586         char buf[1024] = "";
2587
2588         if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || !(vms = get_vm_state_by_imapuser(user, 0))) {
2589                 if (user && option_debug > 2)
2590                         ast_log(AST_LOG_WARNING, "User %s mailbox not found for update.\n", user);
2591                 return;
2592         }
2593
2594         ast_debug(3, "User %s mailbox set for update.\n", user);
2595
2596         vms->updated = 1; /* Set updated flag since mailbox changed */
2597 }
2598
2599 static void init_vm_state(struct vm_state *vms) 
2600 {
2601         int x;
2602         vms->vmArrayIndex = 0;
2603         for (x = 0; x < VMSTATE_MAX_MSG_ARRAY; x++) {
2604                 vms->msgArray[x] = 0;
2605         }
2606         ast_mutex_init(&vms->lock);
2607 }
2608
2609 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro) 
2610 {
2611         char *body_content;
2612         char *body_decoded;
2613         char *fn = is_intro ? vms->introfn : vms->fn;
2614         unsigned long len;
2615         unsigned long newlen;
2616         char filename[256];
2617         
2618         if (!body || body == NIL)
2619                 return -1;
2620
2621         body_content = mail_fetchbody(vms->mailstream, vms->msgArray[vms->curmsg], section, &len);
2622         if (body_content != NIL) {
2623                 snprintf(filename, sizeof(filename), "%s.%s", fn, format);
2624                 /* ast_debug(1,body_content); */
2625                 body_decoded = rfc822_base64((unsigned char *)body_content, len, &newlen);
2626                 /* If the body of the file is empty, return an error */
2627                 if (!newlen) {
2628                         return -1;
2629                 }
2630                 write_file(filename, (char *) body_decoded, newlen);
2631         } else {
2632                 ast_debug(5, "Body of message is NULL.\n");
2633                 return -1;
2634         }
2635         return 0;
2636 }
2637
2638 /*! 
2639  * \brief Get delimiter via mm_list callback 
2640  * \param stream
2641  *
2642  * Determines the delimiter character that is used by the underlying IMAP based mail store.
2643  */
2644 static void get_mailbox_delimiter(MAILSTREAM *stream) {
2645         char tmp[50];
2646         snprintf(tmp, sizeof(tmp), "{%s}", imapserver);
2647         mail_list(stream, tmp, "*");
2648 }
2649
2650 /*! 
2651  * \brief Check Quota for user 
2652  * \param vms a pointer to a vm_state struct, will use the mailstream property of this.
2653  * \param mailbox the mailbox to check the quota for.
2654  *
2655  * Calls imap_getquotaroot, which will populate its results into the vm_state vms input structure.
2656  */
2657 static void check_quota(struct vm_state *vms, char *mailbox) {
2658         mail_parameters(NULL, SET_QUOTA, (void *) mm_parsequota);
2659         ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mailbox);
2660         if (vms && vms->mailstream != NULL) {
2661                 imap_getquotaroot(vms->mailstream, mailbox);
2662         } else {
2663                 ast_log(AST_LOG_WARNING, "Mailstream not available for mailbox: %s\n", mailbox);
2664         }
2665 }
2666
2667 #endif /* IMAP_STORAGE */
2668
2669 /*! \brief Lock file path
2670     only return failure if ast_lock_path returns 'timeout',
2671    not if the path does not exist or any other reason
2672 */
2673 static int vm_lock_path(const char *path)
2674 {
2675         switch (ast_lock_path(path)) {
2676         case AST_LOCK_TIMEOUT:
2677                 return -1;
2678         default:
2679                 return 0;
2680         }
2681 }
2682
2683
2684 #ifdef ODBC_STORAGE
2685 struct generic_prepare_struct {
2686         char *sql;
2687         int argc;
2688         char **argv;
2689 };
2690
2691 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
2692 {
2693         struct generic_prepare_struct *gps = data;
2694         int res, i;
2695         SQLHSTMT stmt;
2696
2697         res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
2698         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2699                 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
2700                 return NULL;
2701         }
2702         res = SQLPrepare(stmt, (unsigned char *)gps->sql, SQL_NTS);
2703         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2704                 ast_log(AST_LOG_WARNING, "SQL Prepare failed![%s]\n", gps->sql);
2705                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
2706                 return NULL;
2707         }
2708         for (i = 0; i < gps->argc; i++)
2709                 SQLBindParameter(stmt, i + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->argv[i]), 0, gps->argv[i], 0, NULL);
2710
2711         return stmt;
2712 }
2713
2714 /*!
2715  * \brief Retrieves a file from an ODBC data store.
2716  * \param dir the path to the file to be retreived.
2717  * \param msgnum the message number, such as within a mailbox folder.
2718  * 
2719  * This method is used by the RETRIEVE macro when mailboxes are stored in an ODBC back end.
2720  * 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.
2721  *
2722  * The file is looked up by invoking a SQL on the odbc_table (default 'voicemessages') using the dir and msgnum input parameters.
2723  * The output is the message information file with the name msgnum and the extension .txt
2724  * and the message file with the extension of its format, in the directory with base file name of the msgnum.
2725  * 
2726  * \return 0 on success, -1 on error.
2727  */
2728 static int retrieve_file(char *dir, int msgnum)
2729 {
2730         int x = 0;
2731         int res;
2732         int fd=-1;
2733         size_t fdlen = 0;
2734         void *fdm = MAP_FAILED;
2735         SQLSMALLINT colcount=0;
2736         SQLHSTMT stmt;
2737         char sql[PATH_MAX];
2738         char fmt[80]="";
2739         char *c;
2740         char coltitle[256];
2741         SQLSMALLINT collen;
2742         SQLSMALLINT datatype;
2743         SQLSMALLINT decimaldigits;
2744         SQLSMALLINT nullable;
2745         SQLULEN colsize;
2746         SQLLEN colsize2;
2747         FILE *f=NULL;
2748         char rowdata[80];
2749         char fn[PATH_MAX];
2750         char full_fn[PATH_MAX];
2751         char msgnums[80];
2752         char *argv[] = { dir, msgnums };
2753         struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
2754
2755         struct odbc_obj *obj;
2756         obj = ast_odbc_request_obj(odbc_database, 0);
2757         if (obj) {
2758                 ast_copy_string(fmt, vmfmts, sizeof(fmt));
2759                 c = strchr(fmt, '|');
2760                 if (c)
2761                         *c = '\0';
2762                 if (!strcasecmp(fmt, "wav49"))
2763                         strcpy(fmt, "WAV");
2764                 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
2765                 if (msgnum > -1)
2766                         make_file(fn, sizeof(fn), dir, msgnum);
2767                 else
2768                         ast_copy_string(fn, dir, sizeof(fn));
2769
2770                 /* Create the information file */
2771                 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
2772                 
2773                 if (!(f = fopen(full_fn, "w+"))) {
2774                         ast_log(AST_LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
2775                         goto yuck;
2776                 }
2777                 
2778                 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
2779                 snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?",odbc_table);
2780                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2781                 if (!stmt) {
2782                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2783                         ast_odbc_release_obj(obj);
2784                         goto yuck;
2785                 }
2786                 res = SQLFetch(stmt);
2787                 if (res == SQL_NO_DATA) {
2788                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2789                         ast_odbc_release_obj(obj);
2790                         goto yuck;
2791                 } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2792                         ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2793                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2794                         ast_odbc_release_obj(obj);
2795                         goto yuck;
2796                 }
2797                 fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
2798                 if (fd < 0) {
2799                         ast_log(AST_LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
2800                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2801                         ast_odbc_release_obj(obj);
2802                         goto yuck;
2803                 }
2804                 res = SQLNumResultCols(stmt, &colcount);
2805                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {   
2806                         ast_log(AST_LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
2807                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2808                         ast_odbc_release_obj(obj);
2809                         goto yuck;
2810                 }
2811                 if (f) 
2812                         fprintf(f, "[message]\n");
2813                 for (x=0;x<colcount;x++) {
2814                         rowdata[0] = '\0';
2815                         collen = sizeof(coltitle);
2816                         res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen, 
2817                                                 &datatype, &colsize, &decimaldigits, &nullable);
2818                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2819                                 ast_log(AST_LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
2820                                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2821                                 ast_odbc_release_obj(obj);
2822                                 goto yuck;
2823                         }
2824                         if (!strcasecmp(coltitle, "recording")) {
2825                                 off_t offset;
2826                                 res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
2827                                 fdlen = colsize2;
2828                                 if (fd > -1) {
2829                                         char tmp[1]="";
2830                                         lseek(fd, fdlen - 1, SEEK_SET);
2831                                         if (write(fd, tmp, 1) != 1) {
2832                                                 close(fd);
2833                                                 fd = -1;
2834                                                 continue;
2835                                         }
2836                                         /* Read out in small chunks */
2837                                         for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
2838                                                 if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
2839                                                         ast_log(AST_LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
2840                                                         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
2841                                                         ast_odbc_release_obj(obj);
2842                                                         goto yuck;
2843                                                 } else {
2844                                                         res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
2845                                                         munmap(fdm, CHUNKSIZE);
2846                                                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2847                                                                 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2848                                                                 unlink(full_fn);
2849                                                                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
2850                                                                 ast_odbc_release_obj(obj);
2851                                                                 goto yuck;
2852                                                         }
2853                                                 }
2854                                         }
2855                                         truncate(full_fn, fdlen);
2856                                 }
2857                         } else {
2858                                 res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2859                                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2860                                         ast_log(AST_LOG_WARNING, "SQL Get Data error! coltitle=%s\n[%s]\n\n", coltitle, sql);
2861                                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2862                                         ast_odbc_release_obj(obj);
2863                                         goto yuck;
2864                                 }
2865                                 if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
2866                                         fprintf(f, "%s=%s\n", coltitle, rowdata);
2867                         }
2868                 }
2869                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2870                 ast_odbc_release_obj(obj);
2871         } else
2872                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2873 yuck:   
2874         if (f)
2875                 fclose(f);
2876         if (fd > -1)
2877                 close(fd);
2878         return x - 1;
2879 }
2880
2881 /*!
2882  * \brief Determines the highest message number in use for a given user and mailbox folder.
2883  * \param vmu 
2884  * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
2885  *
2886  * This method is used when mailboxes are stored in an ODBC back end.
2887  * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
2888  *
2889  * \return the value of zero or greaterto indicate the last message index in use, -1 to indicate none.
2890  */
2891 static int last_message_index(struct ast_vm_user *vmu, char *dir)
2892 {
2893         int x = 0;
2894         int res;
2895         SQLHSTMT stmt;
2896         char sql[PATH_MAX];
2897         char rowdata[20];
2898         char *argv[] = { dir };
2899         struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
2900
2901         struct odbc_obj *obj;
2902         obj = ast_odbc_request_obj(odbc_database, 0);
2903         if (obj) {
2904                 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?",odbc_table);
2905                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2906                 if (!stmt) {
2907                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2908                         ast_odbc_release_obj(obj);
2909                         goto yuck;
2910                 }
2911                 res = SQLFetch(stmt);
2912                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2913                         ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2914                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2915                         ast_odbc_release_obj(obj);
2916                         goto yuck;
2917                 }
2918                 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2919                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2920                         ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2921                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2922                         ast_odbc_release_obj(obj);
2923                         goto yuck;
2924                 }
2925                 if (sscanf(rowdata, "%d", &x) != 1)
2926                         ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
2927                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2928                 ast_odbc_release_obj(obj);
2929         } else
2930                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2931 yuck:   
2932         return x - 1;
2933 }
2934
2935 /*!
2936  * \brief Determines if the specified message exists.
2937  * \param dir the folder the mailbox folder to look for messages. 
2938  * \param msgnum the message index to query for.
2939  *
2940  * This method is used when mailboxes are stored in an ODBC back end.
2941  *
2942  * \return greater than zero if the message exists, zero when the message does not exist or on error.
2943  */
2944 static int message_exists(char *dir, int msgnum)
2945 {
2946         int x = 0;
2947         int res;
2948         SQLHSTMT stmt;
2949         char sql[PATH_MAX];
2950         char rowdata[20];
2951         char msgnums[20];
2952         char *argv[] = { dir, msgnums };
2953         struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
2954
2955         struct odbc_obj *obj;
2956         obj = ast_odbc_request_obj(odbc_database, 0);
2957         if (obj) {
2958                 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
2959                 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?",odbc_table);
2960                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2961                 if (!stmt) {
2962                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2963                         ast_odbc_release_obj(obj);
2964                         goto yuck;
2965                 }
2966                 res = SQLFetch(stmt);
2967                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2968                         ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2969                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2970                         ast_odbc_release_obj(obj);
2971                         goto yuck;
2972                 }
2973                 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2974                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2975                         ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2976                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2977                         ast_odbc_release_obj(obj);
2978                         goto yuck;
2979                 }
2980                 if (sscanf(rowdata, "%d", &x) != 1)
2981                         ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
2982                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2983                 ast_odbc_release_obj(obj);
2984         } else
2985                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2986 yuck:   
2987         return x;
2988 }
2989
2990 /*!
2991  * \brief returns the one-based count for messages.
2992  * \param vmu
2993  * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
2994  *
2995  * This method is used when mailboxes are stored in an ODBC back end.
2996  * The message index is zero-based, the first message will be index 0. For convenient display it is good to have the
2997  * one-based messages.
2998  * This method just calls last_message_index and returns +1 of its value.
2999  *
3000  * \return the value greater than zero on success to indicate the one-based count of messages, less than zero on error.
3001  */
3002 static int count_messages(struct ast_vm_user *vmu, char *dir)
3003 {
3004         return last_message_index(vmu, dir) + 1;
3005 }
3006
3007 /*!
3008  * \brief Deletes a message from the mailbox folder.
3009  * \param sdir The mailbox folder to work in.
3010  * \param smsg The message index to be deleted.
3011  *
3012  * This method is used when mailboxes are stored in an ODBC back end.
3013  * The specified message is directly deleted from the database 'voicemessages' table.
3014  * 
3015  * \return the value greater than zero on success to indicate the number of messages, less than zero on error.
3016  */
3017 static void delete_file(char *sdir, int smsg)
3018 {
3019         SQLHSTMT stmt;
3020         char sql[PATH_MAX];
3021         char msgnums[20];
3022         char *argv[] = { sdir, msgnums };
3023         struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3024
3025         struct odbc_obj *obj;
3026         obj = ast_odbc_request_obj(odbc_database, 0);
3027         if (obj) {
3028                 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3029                 snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?",odbc_table);
3030                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3031                 if (!stmt)
3032                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3033                 else
3034                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3035                 ast_odbc_release_obj(obj);
3036         } else
3037                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3038         return; 
3039 }
3040
3041 /*!
3042  * \brief Copies a voicemail from one mailbox to another.
3043  * \param sdir the folder for which to look for the message to be copied.
3044  * \param smsg the index of the message to be copied.
3045  * \param ddir the destination folder to copy the message into.
3046  * \param dmsg the index to be used for the copied message.
3047  * \param dmailboxuser The user who owns the mailbox tha contains the destination folder.
3048  * \param dmailboxcontext The context for the destination user.
3049  *
3050  * This method is used for the COPY macro when mailboxes are stored in an ODBC back end.
3051  */
3052 static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailboxuser, char *dmailboxcontext)
3053 {
3054         SQLHSTMT stmt;
3055         char sql[512];
3056         char msgnums[20];
3057         char msgnumd[20];
3058         struct odbc_obj *obj;
3059         char *argv[] = { ddir, msgnumd, dmailboxuser, dmailboxcontext, sdir, msgnums };
3060         struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
3061
3062         delete_file(ddir, dmsg);
3063         obj = ast_odbc_request_obj(odbc_database, 0);
3064         if (obj) {
3065                 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3066                 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
3067                 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);
3068                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3069                 if (!stmt)
3070                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
3071                 else
3072                         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3073                 ast_odbc_release_obj(obj);
3074         } else
3075                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3076         return; 
3077 }
3078
3079 struct insert_data {
3080         char *sql;
3081         char *dir;
3082         char *msgnums;
3083         void *data;
3084         SQLLEN datalen;
3085         SQLLEN indlen;
3086         const char *context;
3087         const char *macrocontext;
3088         const char *callerid;
3089         const char *origtime;
3090         const char *duration;
3091         char *mailboxuser;
3092         char *mailboxcontext;
3093         const char *category;
3094         const char *flag;
3095 };
3096
3097 static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
3098 {
3099         struct insert_data *data = vdata;
3100         int res;
3101         SQLHSTMT stmt;
3102
3103         res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
3104         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3105                 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
3106                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3107                 return NULL;
3108         }
3109
3110         SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->dir), 0, (void *)data->dir, 0, NULL);
3111         SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->msgnums), 0, (void *)data->msgnums, 0, NULL);
3112         SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, data->datalen, 0, (void *)data->data, data->datalen, &data->indlen);
3113         SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->context), 0, (void *)data->context, 0, NULL);
3114         SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->macrocontext), 0, (void *)data->macrocontext, 0, NULL);
3115         SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->callerid), 0, (void *)data->callerid, 0, NULL);
3116         SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->origtime), 0, (void *)data->origtime, 0, NULL);
3117         SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->duration), 0, (void *)data->duration, 0, NULL);
3118         SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxuser), 0, (void *)data->mailboxuser, 0, NULL);
3119         SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxcontext), 0, (void *)data->mailboxcontext, 0, NULL);
3120         SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->flag), 0, (void *)data->flag, 0, NULL);
3121         if (!ast_strlen_zero(data->category)) {
3122                 SQLBindParameter(stmt, 12, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->category), 0, (void *)data->category, 0, NULL);
3123         }
3124         res = SQLExecDirect(stmt, (unsigned char *)data->sql, SQL_NTS);
3125         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3126                 ast_log(AST_LOG_WARNING, "SQL Direct Execute failed!\n");
3127                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3128                 return NULL;
3129         }
3130
3131         return stmt;
3132 }
3133
3134 /*!
3135  * \brief Stores a voicemail into the database.
3136  * \param dir the folder the mailbox folder to store the message.
3137  * \param mailboxuser the user owning the mailbox folder.
3138  * \param mailboxcontext
3139  * \param msgnum the message index for the message to be stored.
3140  *
3141  * This method is used when mailboxes are stored in an ODBC back end.
3142  * The message sound file and information file is looked up on the file system. 
3143  * A SQL query is invoked to store the message into the (MySQL) database.
3144  *
3145  * \return the zero on success -1 on error.
3146  */
3147 static int store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum)
3148 {
3149         int res = 0;
3150         int fd = -1;
3151         void *fdm = MAP_FAILED;
3152         size_t fdlen = -1;
3153         SQLHSTMT stmt;
3154         char sql[PATH_MAX];
3155         char msgnums[20];
3156         char fn[PATH_MAX];
3157         char full_fn[PATH_MAX];
3158         char fmt[80]="";
3159         char *c;
3160         struct ast_config *cfg=NULL;
3161         struct odbc_obj *obj;
3162         struct insert_data idata = { .sql = sql, .msgnums = msgnums, .dir = dir, .mailboxuser = mailboxuser, .mailboxcontext = mailboxcontext };
3163         struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
3164
3165         delete_file(dir, msgnum);
3166         if (!(obj = ast_odbc_request_obj(odbc_database, 0))) {
3167                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3168                 return -1;
3169         }
3170
3171         do {
3172                 ast_copy_string(fmt, vmfmts, sizeof(fmt));
3173                 c = strchr(fmt, '|');
3174                 if (c)
3175                         *c = '\0';
3176                 if (!strcasecmp(fmt, "wav49"))
3177                         strcpy(fmt, "WAV");
3178                 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
3179                 if (msgnum > -1)
3180                         make_file(fn, sizeof(fn), dir, msgnum);
3181                 else
3182                         ast_copy_string(fn, dir, sizeof(fn));
3183                 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
3184                 cfg = ast_config_load(full_fn, config_flags);
3185                 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
3186                 fd = open(full_fn, O_RDWR);
3187                 if (fd < 0) {
3188                         ast_log(AST_LOG_WARNING, "Open of sound file '%s' failed: %s\n", full_fn, strerror(errno));
3189                         res = -1;
3190                         break;
3191                 }
3192                 if (cfg && cfg != CONFIG_STATUS_FILEINVALID) {
3193                         if (!(idata.context = ast_variable_retrieve(cfg, "message", "context"))) {
3194                                 idata.context = "";
3195                         }
3196                         if (!(idata.macrocontext = ast_variable_retrieve(cfg, "message", "macrocontext"))) {
3197                                 idata.macrocontext = "";
3198                         }
3199                         if (!(idata.callerid = ast_variable_retrieve(cfg, "message", "callerid"))) {
3200                                 idata.callerid = "";
3201                         }
3202                         if (!(idata.origtime = ast_variable_retrieve(cfg, "message", "origtime"))) {
3203                                 idata.origtime = "";
3204                         }
3205                         if (!(idata.duration = ast_variable_retrieve(cfg, "message", "duration"))) {
3206                                 idata.duration = "";
3207                         }
3208                         if (!(idata.category = ast_variable_retrieve(cfg, "message", "category"))) {
3209                                 idata.category = "";
3210                         }
3211                         if (!(idata.flag = ast_variable_retrieve(cfg, "message", "flag"))) {
3212                                 idata.flag = "";
3213                         }
3214                 }
3215                 fdlen = lseek(fd, 0, SEEK_END);
3216                 lseek(fd, 0, SEEK_SET);
3217                 printf("Length is %zd\n", fdlen);
3218                 fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0);
3219                 if (fdm == MAP_FAILED) {
3220                         ast_log(AST_LOG_WARNING, "Memory map failed!\n");
3221                         res = -1;
3222                         break;
3223                 } 
3224                 idata.data = fdm;
3225                 idata.datalen = idata.indlen = fdlen;
3226
3227                 if (!ast_strlen_zero(idata.category)) 
3228                         snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,category) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",odbc_table); 
3229                 else
3230                         snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag) VALUES (?,?,?,?,?,?,?,?,?,?,?)",odbc_table);
3231
3232                 if ((stmt = ast_odbc_direct_execute(obj, insert_data_cb, &idata))) {
3233                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3234                 } else {
3235                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3236                         res = -1;
3237                 }
3238         } while (0);
3239         if (obj) {
3240                 ast_odbc_release_obj(obj);
3241         }
3242         if (cfg)
3243                 ast_config_destroy(cfg);
3244         if (fdm != MAP_FAILED)
3245                 munmap(fdm, fdlen);
3246         if (fd > -1)
3247                 close(fd);
3248         return res;
3249 }
3250
3251 /*!
3252  * \brief Renames a message in a mailbox folder.
3253  * \param sdir The folder of the message to be renamed.
3254  * \param smsg The index of the message to be renamed.
3255  * \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.
3256  * \param mailboxcontext The context to be set for the message. Usually this will be the same as the original context.
3257  * \param ddir The destination folder for the message to be renamed into
3258  * \param dmsg The destination message for the message to be renamed.
3259  *
3260  * This method is used by the RENAME macro when mailboxes are stored in an ODBC back end.
3261  * 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.
3262  * 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.
3263  */
3264 static void rename_file(char *sdir, int smsg, char *mailboxuser, char *mailboxcontext, char *ddir, int dmsg)
3265 {
3266         SQLHSTMT stmt;
3267         char sql[PATH_MAX];
3268         char msgnums[20];
3269         char msgnumd[20];
3270         struct odbc_obj *obj;
3271         char *argv[] = { ddir, msgnumd, mailboxuser, mailboxcontext, sdir, msgnums };
3272         struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
3273
3274         delete_file(ddir, dmsg);
3275         obj = ast_odbc_request_obj(odbc_database, 0);
3276         if (obj) {
3277                 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3278                 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
3279                 snprintf(sql, sizeof(sql), "UPDATE %s SET dir=?, msgnum=?, mailboxuser=?, mailboxcontext=? WHERE dir=? AND msgnum=?",odbc_table);
3280                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3281                 if (!stmt)
3282                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3283                 else
3284                         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3285                 ast_odbc_release_obj(obj);
3286         } else
3287                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3288         return; 
3289 }
3290
3291 /*!
3292  * \brief Removes a voicemail message file.
3293  * \param dir the path to the message file.
3294  * \param msgnum the unique number for the message within the mailbox.
3295  *
3296  * Removes the message content file and the information file.
3297  * This method is used by the DISPOSE macro when mailboxes are stored in an ODBC back end.
3298  * Typical use is to clean up after a RETRIEVE operation. 
3299  * Note that this does not remove the message from the mailbox folders, to do that we would use delete_file().
3300  * \return zero on success, -1 on error.
3301  */
3302 static int remove_file(char *dir, int msgnum)
3303 {
3304         char fn[PATH_MAX];
3305         char full_fn[PATH_MAX];
3306         char msgnums[80];
3307         
3308         if (msgnum > -1) {
3309                 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
3310                 make_file(fn, sizeof(fn), dir, msgnum);
3311         } else
3312                 ast_copy_string(fn, dir, sizeof(fn));
3313         ast_filedelete(fn, NULL);       
3314         snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
3315         unlink(full_fn);
3316         return 0;
3317 }
3318 #else
3319 #ifndef IMAP_STORAGE
3320 /*!
3321  * \brief Find all .txt files - even if they are not in sequence from 0000.
3322  * \param vmu
3323  * \param dir
3324  *
3325  * This method is used when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
3326  *
3327  * \return the count of messages, zero or more.
3328  */
3329 static int count_messages(struct ast_vm_user *vmu, char *dir)
3330 {
3331
3332         int vmcount = 0;
3333         DIR *vmdir = NULL;
3334         struct dirent *vment = NULL;
3335
3336         if (vm_lock_path(dir))
3337                 return ERROR_LOCK_PATH;
3338
3339         if ((vmdir = opendir(dir))) {
3340                 while ((vment = readdir(vmdir))) {
3341                         if (strlen(vment->d_name) > 7 && !strncmp(vment->d_name + 7, ".txt", 4)) {
3342                                 vmcount++;
3343                         }
3344                 }
3345                 closedir(vmdir);
3346         }
3347         ast_unlock_path(dir);
3348         
3349         return vmcount;
3350 }
3351
3352 /*!
3353  * \brief Renames a message in a mailbox folder.
3354  * \param sfn The path to the mailbox information and data file to be renamed.
3355  * \param dfn The path for where the message data and information files will be renamed to.
3356  *
3357  * This method is used by the RENAME macro when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
3358  */
3359 static void rename_file(char *sfn, char *dfn)
3360 {
3361         char stxt[PATH_MAX];
3362         char dtxt[PATH_MAX];
3363         ast_filerename(sfn,dfn,NULL);
3364         snprintf(stxt, sizeof(stxt), "%s.txt", sfn);
3365         snprintf(dtxt, sizeof(dtxt), "%s.txt", dfn);
3366         if (ast_check_realtime("voicemail_data")) {
3367                 ast_update_realtime("voicemail_data", "filename", sfn, "filename", dfn, SENTINEL);
3368         }
3369         rename(stxt, dtxt);
3370 }
3371
3372 /*! 
3373  * \brief Determines the highest message number in use for a given user and mailbox folder.
3374  * \param vmu 
3375  * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
3376  *
3377  * This method is used when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
3378  * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
3379  *
3380  * \note Should always be called with a lock already set on dir.
3381  * \return the value of zero or greaterto indicate the last message index in use, -1 to indicate none.
3382  */
3383 static int last_message_index(struct ast_vm_user *vmu, char *dir)
3384 {
3385         int x;
3386         unsigned char map[MAXMSGLIMIT] = "";
3387         DIR *msgdir;
3388         struct dirent *msgdirent;
3389         int msgdirint;
3390
3391         /* Reading the entire directory into a file map scales better than
3392          * doing a stat repeatedly on a predicted sequence.  I suspect this
3393          * is partially due to stat(2) internally doing a readdir(2) itself to
3394          * find each file. */
3395         msgdir = opendir(dir);
3396         while ((msgdirent = readdir(msgdir))) {
3397                 if (sscanf(msgdirent->d_name, "msg%d", &msgdirint) == 1 && msgdirint < MAXMSGLIMIT)
3398                         map[msgdirint] = 1;
3399         }
3400         closedir(msgdir);
3401
3402         for (x = 0; x < vmu->maxmsg; x++) {
3403                 if (map[x] == 0)
3404                         break;
3405         }
3406
3407         return x - 1;
3408 }
3409
3410 #endif /* #ifndef IMAP_STORAGE */
3411 #endif /* #else of #ifdef ODBC_STORAGE */
3412 #ifndef IMAP_STORAGE
3413 /*!
3414  * \brief Utility function to copy a file.
3415  * \param infile The path to the file to be copied. The file must be readable, it is opened in read only mode.
3416  * \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.
3417  *
3418  * 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.
3419  * The copy operation copies up to 4096 bytes at once.
3420  *
3421  * \return zero on success, -1 on error.
3422  */
3423 static int copy(char *infile, char *outfile)
3424 {
3425         int ifd;
3426         int ofd;
3427         int res;
3428         int len;
3429         char buf[4096];
3430
3431 #ifdef HARDLINK_WHEN_POSSIBLE
3432         /* Hard link if possible; saves disk space & is faster */
3433         if (link(infile, outfile)) {
3434 #endif
3435                 if ((ifd = open(infile, O_RDONLY)) < 0) {
3436                         ast_log(AST_LOG_WARNING, "Unable to open %s in read-only mode: %s\n", infile, strerror(errno));
3437                         return -1;
3438                 }
3439                 if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, VOICEMAIL_FILE_MODE)) < 0) {
3440                         ast_log(AST_LOG_WARNING, "Unable to open %s in write-only mode: %s\n", outfile, strerror(errno));
3441                         close(ifd);
3442                         return -1;
3443                 }
3444                 do {
3445                         len = read(ifd, buf, sizeof(buf));
3446                         if (len < 0) {
3447                                 ast_log(AST_LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
3448                                 close(ifd);
3449                                 close(ofd);
3450                                 unlink(outfile);
3451                         }
3452                         if (len) {
3453                                 res = write(ofd, buf, len);
3454                                 if (errno == ENOMEM || errno == ENOSPC || res != len) {
3455                                         ast_log(AST_LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno));
3456                                         close(ifd);
3457                                         close(ofd);
3458                                         unlink(outfile);
3459                                 }
3460                         }
3461                 } while (len);
3462                 close(ifd);
3463                 close(ofd);
3464                 return 0;
3465 #ifdef HARDLINK_WHEN_POSSIBLE
3466         } else {
3467                 /* Hard link succeeded */
3468                 return 0;
3469         }
3470 #endif
3471 }
3472
3473 /*!
3474  * \brief Copies a voicemail information (envelope) file.
3475  * \param frompath
3476  * \param topath 
3477  *
3478  * Every voicemail has the data (.wav) file, and the information file.
3479  * This function performs the file system copying of the information file for a voicemail, handling the internal fields and their values.
3480  * This is used by the COPY macro when not using IMAP storage.
3481  */
3482 static void copy_plain_file(char *frompath, char *topath)
3483 {
3484         char frompath2[PATH_MAX], topath2[PATH_MAX];
3485         struct ast_variable *tmp,*var = NULL;
3486         const char *origmailbox = NULL, *context = NULL, *macrocontext = NULL, *exten = NULL, *priority = NULL, *callerchan = NULL, *callerid = NULL, *origdate = NULL, *origtime = NULL, *category = NULL, *duration = NULL;
3487         ast_filecopy(frompath, topath, NULL);
3488         snprintf(frompath2, sizeof(frompath2), "%s.txt", frompath);
3489         snprintf(topath2, sizeof(topath2), "%s.txt", topath);
3490         if (ast_check_realtime("voicemail_data")) {
3491                 var = ast_load_realtime("voicemail_data", "filename", frompath, SENTINEL);
3492                 /* This cycle converts ast_variable linked list, to va_list list of arguments, may be there is a better way to do it? */
3493                 for (tmp = var; tmp; tmp = tmp->next) {
3494                         if (!strcasecmp(tmp->name, "origmailbox")) {
3495                                 origmailbox = tmp->value;
3496                         } else if (!strcasecmp(tmp->name, "context")) {
3497                                 context = tmp->value;
3498                         } else if (!strcasecmp(tmp->name, "macrocontext")) {
3499                                 macrocontext = tmp->value;
3500                         } else if (!strcasecmp(tmp->name, "exten")) {
3501                                 exten = tmp->value;
3502                         } else if (!strcasecmp(tmp->name, "priority")) {
3503                                 priority = tmp->value;
3504                         } else if (!strcasecmp(tmp->name, "callerchan")) {
3505                                 callerchan = tmp->value;
3506                         } else if (!strcasecmp(tmp->name, "callerid")) {
3507                                 callerid = tmp->value;
3508                         } else if (!strcasecmp(tmp->name, "origdate")) {
3509                                 origdate = tmp->value;
3510                         } else if (!strcasecmp(tmp->name, "origtime")) {
3511                                 origtime = tmp->value;
3512                         } else if (!strcasecmp(tmp->name, "category")) {
3513                                 category = tmp->value;
3514                         } else if (!strcasecmp(tmp->name, "duration")) {
3515                                 duration = tmp->value;
3516                         }
3517                 }
3518                 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);
3519         }
3520         copy(frompath2, topath2);
3521         ast_variables_destroy(var);
3522 }
3523
3524 /*! 
3525  * \brief Removes the voicemail sound and information file.
3526  * \param file The path to the sound file. This will be the the folder and message index, without the extension.
3527  *
3528  * This is used by the DELETE macro when voicemails are stored on the file system.
3529  *
3530  * \return zero on success, -1 on error.
3531  */
3532 static int vm_delete(char *file)
3533 {
3534         char *txt;
3535         int txtsize = 0;
3536
3537         txtsize = (strlen(file) + 5)*sizeof(char);
3538         txt = alloca(txtsize);
3539         /* Sprintf here would safe because we alloca'd exactly the right length,
3540          * but trying to eliminate all sprintf's anyhow
3541          */
3542         if (ast_check_realtime("voicemail_data")) {
3543                 ast_destroy_realtime("voicemail_data", "filename", file, SENTINEL);
3544         }
3545         snprintf(txt, txtsize, "%s.txt", file);
3546         unlink(txt);
3547         return ast_filedelete(file, NULL);
3548 }
3549 #endif
3550
3551 /*!
3552  * \brief utility used by inchar(), for base_encode()
3553  */
3554 static int inbuf(struct baseio *bio, FILE *fi)
3555 {
3556         int l;
3557
3558         if (bio->ateof)
3559                 return 0;
3560
3561         if ((l = fread(bio->iobuf,1,BASEMAXINLINE,fi)) <= 0) {
3562                 if (ferror(fi))
3563                         return -1;
3564
3565                 bio->ateof = 1;
3566                 return 0;
3567         }
3568
3569         bio->iolen= l;
3570         bio->iocp= 0;
3571
3572         return 1;
3573 }
3574
3575 /*!
3576  * \brief utility used by base_encode()
3577  */
3578 static int inchar(struct baseio *bio, FILE *fi)
3579 {
3580         if (bio->iocp>=bio->iolen) {
3581                 if (!inbuf(bio, fi))
3582                         return EOF;
3583         }
3584
3585         return bio->iobuf[bio->iocp++];
3586 }
3587
3588 /*!
3589  * \brief utility used by base_encode()
3590  */
3591 static int ochar(struct baseio *bio, int c, FILE *so)
3592 {
3593         if (bio->linelength >= BASELINELEN) {
3594                 if (fputs(eol,so) == EOF)
3595                         return -1;
3596
3597                 bio->linelength= 0;
3598         }
3599
3600         if (putc(((unsigned char)c),so) == EOF)
3601                 return -1;
3602
3603         bio->linelength++;
3604
3605         return 1;
3606 }
3607
3608 /*!
3609  * \brief Performs a base 64 encode algorithm on the contents of a File
3610  * \param filename The path to the file to be encoded. Must be readable, file is opened in read mode.
3611  * \param so A FILE handle to the output file to receive the base 64 encoded contents of the input file, identified by filename.
3612  *
3613  * 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 ?
3614  *
3615  * \return zero on success, -1 on error.
3616  */
3617 static int base_encode(char *filename, FILE *so)
3618 {
3619         static const unsigned char dtable[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
3620                 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
3621                 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0',
3622                 '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
3623         int i,hiteof= 0;
3624         FILE *fi;
3625         struct baseio bio;
3626
3627         memset(&bio, 0, sizeof(bio));
3628         bio.iocp = BASEMAXINLINE;
3629
3630         if (!(fi = fopen(filename, "rb"))) {
3631                 ast_log(AST_LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno));
3632                 return -1;
3633         }
3634
3635         while (!hiteof){
3636                 unsigned char igroup[3], ogroup[4];
3637                 int c,n;
3638
3639                 igroup[0]= igroup[1]= igroup[2]= 0;
3640
3641                 for (n= 0;n<3;n++) {
3642                         if ((c = inchar(&bio, fi)) == EOF) {
3643                                 hiteof= 1;
3644                                 break;
3645                         }
3646
3647                         igroup[n]= (unsigned char)c;
3648                 }
3649
3650                 if (n> 0) {
3651                         ogroup[0]= dtable[igroup[0]>>2];
3652                         ogroup[1]= dtable[((igroup[0]&3)<<4) | (igroup[1]>>4)];
3653                         ogroup[2]= dtable[((igroup[1]&0xF)<<2) | (igroup[2]>>6)];
3654                         ogroup[3]= dtable[igroup[2]&0x3F];
3655
3656