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