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