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