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