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