Fix breakage caused by #12028. (Closes issue #12535)
[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 /*!
1625  * \brief Stores a voicemail into the database.
1626  * \param dir the folder the mailbox folder to store the message.
1627  * \param mailboxuser the user owning the mailbox folder.
1628  * \param mailboxcontext
1629  * \param msgnum the message index for the message to be stored.
1630  *
1631  * This method is used when mailboxes are stored in an ODBC back end.
1632  * The message sound file and information file is looked up on the file system. 
1633  * A SQL query is invoked to store the message into the (MySQL) database.
1634  *
1635  * \return the zero on success -1 on error.
1636  */
1637 static int store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum)
1638 {
1639         int x = 0;
1640         int res;
1641         int fd = -1;
1642         void *fdm = MAP_FAILED;
1643         size_t fdlen = -1;
1644         SQLHSTMT stmt;
1645         SQLLEN len;
1646         char sql[PATH_MAX];
1647         char msgnums[20];
1648         char fn[PATH_MAX];
1649         char full_fn[PATH_MAX];
1650         char fmt[80] = "";
1651         char *c;
1652         const char *context = "";
1653         const char *macrocontext = "";
1654         const char *callerid = "";
1655         const char *origtime = ""; 
1656         const char *duration = "";
1657         const char *category = "";
1658         struct ast_config *cfg = NULL;
1659         struct odbc_obj *obj;
1660         struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
1661
1662         delete_file(dir, msgnum);
1663         obj = ast_odbc_request_obj(odbc_database, 0);
1664         if (obj) {
1665                 ast_copy_string(fmt, vmfmts, sizeof(fmt));
1666                 c = strchr(fmt, '|');
1667                 if (c)
1668                         *c = '\0';
1669                 if (!strcasecmp(fmt, "wav49"))
1670                         strcpy(fmt, "WAV");
1671                 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
1672                 if (msgnum > -1)
1673                         make_file(fn, sizeof(fn), dir, msgnum);
1674                 else
1675                         ast_copy_string(fn, dir, sizeof(fn));
1676                 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
1677                 cfg = ast_config_load(full_fn, config_flags);
1678                 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
1679                 fd = open(full_fn, O_RDWR);
1680                 if (fd < 0) {
1681                         ast_log(AST_LOG_WARNING, "Open of sound file '%s' failed: %s\n", full_fn, strerror(errno));
1682                         ast_odbc_release_obj(obj);
1683                         goto yuck;
1684                 }
1685                 if (cfg) {
1686                         context = ast_variable_retrieve(cfg, "message", "context");
1687                         if (!context) context = "";
1688                         macrocontext = ast_variable_retrieve(cfg, "message", "macrocontext");
1689                         if (!macrocontext) macrocontext = "";
1690                         callerid = ast_variable_retrieve(cfg, "message", "callerid");
1691                         if (!callerid) callerid = "";
1692                         origtime = ast_variable_retrieve(cfg, "message", "origtime");
1693                         if (!origtime) origtime = "";
1694                         duration = ast_variable_retrieve(cfg, "message", "duration");
1695                         if (!duration) duration = "";
1696                         category = ast_variable_retrieve(cfg, "message", "category");
1697                         if (!category) category = "";
1698                 }
1699                 fdlen = lseek(fd, 0, SEEK_END);
1700                 lseek(fd, 0, SEEK_SET);
1701                 printf("Length is %zd\n", fdlen);
1702                 fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
1703                 if (fdm == MAP_FAILED) {
1704                         ast_log(AST_LOG_WARNING, "Memory map failed!\n");
1705                         ast_odbc_release_obj(obj);
1706                         goto yuck;
1707                 } 
1708                 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1709                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1710                         ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
1711                         ast_odbc_release_obj(obj);
1712                         goto yuck;
1713                 }
1714                 if (!ast_strlen_zero(category)) 
1715                         snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,category) VALUES (?,?,?,?,?,?,?,?,?,?,?)", odbc_table);
1716                 else
1717                         snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext) VALUES (?,?,?,?,?,?,?,?,?,?)", odbc_table);
1718                 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
1719                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1720                         ast_log(AST_LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1721                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1722                         ast_odbc_release_obj(obj);
1723                         goto yuck;
1724                 }
1725                 len = fdlen; /* SQL_LEN_DATA_AT_EXEC(fdlen); */
1726                 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
1727                 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1728                 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, fdlen, 0, (void *)fdm, fdlen, &len);
1729                 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(context), 0, (void *)context, 0, NULL);
1730                 SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(macrocontext), 0, (void *)macrocontext, 0, NULL);
1731                 SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(callerid), 0, (void *)callerid, 0, NULL);
1732                 SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(origtime), 0, (void *)origtime, 0, NULL);
1733                 SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(duration), 0, (void *)duration, 0, NULL);
1734                 SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxuser), 0, (void *)mailboxuser, 0, NULL);
1735                 SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxcontext), 0, (void *)mailboxcontext, 0, NULL);
1736                 if (!ast_strlen_zero(category))
1737                         SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(category), 0, (void *)category, 0, NULL);
1738                 res = ast_odbc_smart_execute(obj, stmt);
1739                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1740                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1741                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1742                         ast_odbc_release_obj(obj);
1743                         goto yuck;
1744                 }
1745                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1746                 ast_odbc_release_obj(obj);
1747         } else
1748                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1749 yuck:   
1750         if (cfg)
1751                 ast_config_destroy(cfg);
1752         if (fdm != MAP_FAILED)
1753                 munmap(fdm, fdlen);
1754         if (fd > -1)
1755                 close(fd);
1756         return x;
1757 }
1758
1759 /*!
1760  * \brief Renames a message in a mailbox folder.
1761  * \param sdir The folder of the message to be renamed.
1762  * \param smsg The index of the message to be renamed.
1763  * \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.
1764  * \param mailboxcontext The context to be set for the message. Usually this will be the same as the original context.
1765  * \param ddir The destination folder for the message to be renamed into
1766  * \param dmsg The destination message for the message to be renamed.
1767  *
1768  * This method is used by the RENAME macro when mailboxes are stored in an ODBC back end.
1769  * 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.
1770  * 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.
1771  */
1772 static void rename_file(char *sdir, int smsg, char *mailboxuser, char *mailboxcontext, char *ddir, int dmsg)
1773 {
1774         SQLHSTMT stmt;
1775         char sql[PATH_MAX];
1776         char msgnums[20];
1777         char msgnumd[20];
1778         struct odbc_obj *obj;
1779         char *argv[] = { ddir, msgnumd, mailboxuser, mailboxcontext, sdir, msgnums };
1780         struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
1781
1782         delete_file(ddir, dmsg);
1783         obj = ast_odbc_request_obj(odbc_database, 0);
1784         if (obj) {
1785                 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1786                 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
1787                 snprintf(sql, sizeof(sql), "UPDATE %s SET dir=?, msgnum=?, mailboxuser=?, mailboxcontext=? WHERE dir=? AND msgnum=?", odbc_table);
1788                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1789                 if (!stmt)
1790                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1791                 else
1792                         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1793                 ast_odbc_release_obj(obj);
1794         } else
1795                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1796         return; 
1797 }
1798
1799 #else
1800 #ifndef IMAP_STORAGE
1801 /*!
1802  * \brief Find all .txt files - even if they are not in sequence from 0000.
1803  * \param vmu
1804  * \param dir
1805  *
1806  * This method is used when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
1807  *
1808  * \return the count of messages, zero or more.
1809  */
1810 static int count_messages(struct ast_vm_user *vmu, char *dir)
1811 {
1812
1813         int vmcount = 0;
1814         DIR *vmdir = NULL;
1815         struct dirent *vment = NULL;
1816
1817         if (vm_lock_path(dir))
1818                 return ERROR_LOCK_PATH;
1819
1820         if ((vmdir = opendir(dir))) {
1821                 while ((vment = readdir(vmdir))) {
1822                         if (strlen(vment->d_name) > 7 && !strncmp(vment->d_name + 7, ".txt", 4)) 
1823                                 vmcount++;
1824                 }
1825                 closedir(vmdir);
1826         }
1827         ast_unlock_path(dir);
1828         
1829         return vmcount;
1830 }
1831
1832 /*!
1833  * \brief Renames a message in a mailbox folder.
1834  * \param sfn The path to the mailbox information and data file to be renamed.
1835  * \param dfn The path for where the message data and information files will be renamed to.
1836  *
1837  * This method is used by the RENAME macro when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
1838  */
1839 static void rename_file(char *sfn, char *dfn)
1840 {
1841         char stxt[PATH_MAX];
1842         char dtxt[PATH_MAX];
1843         ast_filerename(sfn, dfn, NULL);
1844         snprintf(stxt, sizeof(stxt), "%s.txt", sfn);
1845         snprintf(dtxt, sizeof(dtxt), "%s.txt", dfn);
1846         if (ast_check_realtime("voicemail_data")) {
1847                 ast_update_realtime("voicemail_data", "filename", sfn, "filename", dfn, NULL);
1848         }
1849         rename(stxt, dtxt);
1850 }
1851 #endif
1852
1853 #ifndef IMAP_STORAGE
1854 /*! 
1855  * \brief Determines the highest message number in use for a given user and mailbox folder.
1856  * \param vmu 
1857  * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
1858  *
1859  * This method is used when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
1860  * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
1861  *
1862  * \note Should always be called with a lock already set on dir.
1863  * \return the value of zero or greaterto indicate the last message index in use, -1 to indicate none.
1864  */
1865 static int last_message_index(struct ast_vm_user *vmu, char *dir)
1866 {
1867         int x;
1868         unsigned char map[MAXMSGLIMIT] = "";
1869         DIR *msgdir;
1870         struct dirent *msgdirent;
1871         int msgdirint;
1872
1873         /* Reading the entire directory into a file map scales better than
1874          * doing a stat repeatedly on a predicted sequence.  I suspect this
1875          * is partially due to stat(2) internally doing a readdir(2) itself to
1876          * find each file. */
1877         msgdir = opendir(dir);
1878         while ((msgdirent = readdir(msgdir))) {
1879                 if (sscanf(msgdirent->d_name, "msg%d", &msgdirint) == 1 && msgdirint < MAXMSGLIMIT)
1880                         map[msgdirint] = 1;
1881         }
1882         closedir(msgdir);
1883
1884         for (x = 0; x < vmu->maxmsg; x++) {
1885                 if (map[x] == 0)
1886                         break;
1887         }
1888
1889         return x - 1;
1890 }
1891
1892 #endif /* #ifndef IMAP_STORAGE */
1893 #endif /* #else of #ifdef ODBC_STORAGE */
1894
1895 /*!
1896  * \brief Utility function to copy a file.
1897  * \param infile The path to the file to be copied. The file must be readable, it is opened in read only mode.
1898  * \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.
1899  *
1900  * 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.
1901  * The copy operation copies up to 4096 bytes at once.
1902  *
1903  * \return zero on success, -1 on error.
1904  */
1905 static int copy(char *infile, char *outfile)
1906 {
1907         int ifd;
1908         int ofd;
1909         int res;
1910         int len;
1911         char buf[4096];
1912
1913 #ifdef HARDLINK_WHEN_POSSIBLE
1914         /* Hard link if possible; saves disk space & is faster */
1915         if (link(infile, outfile)) {
1916 #endif
1917                 if ((ifd = open(infile, O_RDONLY)) < 0) {
1918                         ast_log(AST_LOG_WARNING, "Unable to open %s in read-only mode\n", infile);
1919                         return -1;
1920                 }
1921                 if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, VOICEMAIL_FILE_MODE)) < 0) {
1922                         ast_log(AST_LOG_WARNING, "Unable to open %s in write-only mode\n", outfile);
1923                         close(ifd);
1924                         return -1;
1925                 }
1926                 do {
1927                         len = read(ifd, buf, sizeof(buf));
1928                         if (len < 0) {
1929                                 ast_log(AST_LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
1930                                 close(ifd);
1931                                 close(ofd);
1932                                 unlink(outfile);
1933                         }
1934                         if (len) {
1935                                 res = write(ofd, buf, len);
1936                                 if (errno == ENOMEM || errno == ENOSPC || res != len) {
1937                                         ast_log(AST_LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno));
1938                                         close(ifd);
1939                                         close(ofd);
1940                                         unlink(outfile);
1941                                 }
1942                         }
1943                 } while (len);
1944                 close(ifd);
1945                 close(ofd);
1946                 return 0;
1947 #ifdef HARDLINK_WHEN_POSSIBLE
1948         } else {
1949                 /* Hard link succeeded */
1950                 return 0;
1951         }
1952 #endif
1953 }
1954
1955 /*!
1956  * \brief Copies a voicemail information (envelope) file.
1957  * \param frompath
1958  * \param topath 
1959  *
1960  * Every voicemail has the data (.wav) file, and the information file.
1961  * This function performs the file system copying of the information file for a voicemail, handling the internal fields and their values.
1962  * This is used by the COPY macro when not using IMAP storage.
1963  */
1964 static void copy_plain_file(char *frompath, char *topath)
1965 {
1966         char frompath2[PATH_MAX], topath2[PATH_MAX];
1967         struct ast_variable *tmp, *var = NULL;
1968         const char *origmailbox = NULL, *context = NULL, *macrocontext = NULL, *exten = NULL, *priority = NULL, *callerchan = NULL, *callerid = NULL, *origdate = NULL, *origtime = NULL, *category = NULL, *duration = NULL;
1969         ast_filecopy(frompath, topath, NULL);
1970         snprintf(frompath2, sizeof(frompath2), "%s.txt", frompath);
1971         snprintf(topath2, sizeof(topath2), "%s.txt", topath);
1972         if (ast_check_realtime("voicemail_data")) {
1973                 var = ast_load_realtime("voicemail_data", "filename", frompath, NULL);
1974                 /* This cycle converts ast_variable linked list, to va_list list of arguments, may be there is a better way to do it? */
1975                 for (tmp = var; tmp; tmp = tmp->next) {
1976                         if (!strcasecmp(tmp->name, "origmailbox")) {
1977                                 origmailbox = tmp->value;
1978                         } else if (!strcasecmp(tmp->name, "context")) {
1979                                 context = tmp->value;
1980                         } else if (!strcasecmp(tmp->name, "macrocontext")) {
1981                                 macrocontext = tmp->value;
1982                         } else if (!strcasecmp(tmp->name, "exten")) {
1983                                 exten = tmp->value;
1984                         } else if (!strcasecmp(tmp->name, "priority")) {
1985                                 priority = tmp->value;
1986                         } else if (!strcasecmp(tmp->name, "callerchan")) {
1987                                 callerchan = tmp->value;
1988                         } else if (!strcasecmp(tmp->name, "callerid")) {
1989                                 callerid = tmp->value;
1990                         } else if (!strcasecmp(tmp->name, "origdate")) {
1991                                 origdate = tmp->value;
1992                         } else if (!strcasecmp(tmp->name, "origtime")) {
1993                                 origtime = tmp->value;
1994                         } else if (!strcasecmp(tmp->name, "category")) {
1995                                 category = tmp->value;
1996                         } else if (!strcasecmp(tmp->name, "duration")) {
1997                                 duration = tmp->value;
1998                         }
1999                 }
2000                 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);
2001         }
2002         copy(frompath2, topath2);
2003         ast_variables_destroy(var);
2004 }
2005
2006 /*! 
2007  * \brief Removes the voicemail sound and information file.
2008  * \param file The path to the sound file. This will be the the folder and message index, without the extension.
2009  *
2010  * This is used by the DELETE macro when voicemails are stored on the file system.
2011  *
2012  * \return zero on success, -1 on error.
2013  */
2014 static int vm_delete(char *file)
2015 {
2016         char *txt;
2017         int txtsize = 0;
2018
2019         txtsize = (strlen(file) + 5) * sizeof(char);
2020         txt = alloca(txtsize);
2021         /* Sprintf here would safe because we alloca'd exactly the right length,
2022          * but trying to eliminate all sprintf's anyhow
2023          */
2024         if (ast_check_realtime("voicemail_data")) {
2025                 ast_destroy_realtime("voicemail_data", "filename", file, NULL);
2026         }
2027         snprintf(txt, txtsize, "%s.txt", file);
2028         unlink(txt);
2029         return ast_filedelete(file, NULL);
2030 }
2031
2032 /*!
2033  * \brief utility used by inchar(), for base_encode()
2034  */
2035 static int inbuf(struct baseio *bio, FILE *fi)
2036 {
2037         int l;
2038
2039         if (bio->ateof)
2040                 return 0;
2041
2042         if ((l = fread(bio->iobuf, 1, BASEMAXINLINE, fi)) <= 0) {
2043                 if (ferror(fi))
2044                         return -1;
2045
2046                 bio->ateof = 1;
2047                 return 0;
2048         }
2049
2050         bio->iolen = l;
2051         bio->iocp = 0;
2052
2053         return 1;
2054 }
2055
2056 /*!
2057  * \brief utility used by base_encode()
2058  */
2059 static int inchar(struct baseio *bio, FILE *fi)
2060 {
2061         if (bio->iocp >= bio->iolen) {
2062                 if (!inbuf(bio, fi))
2063                         return EOF;
2064         }
2065
2066         return bio->iobuf[bio->iocp++];
2067 }
2068
2069 /*!
2070  * \brief utility used by base_encode()
2071  */
2072 static int ochar(struct baseio *bio, int c, FILE *so)
2073 {
2074         if (bio->linelength >= BASELINELEN) {
2075                 if (fputs(eol, so) == EOF)
2076                         return -1;
2077
2078                 bio->linelength= 0;
2079         }
2080
2081         if (putc(((unsigned char)c), so) == EOF)
2082                 return -1;
2083
2084         bio->linelength++;
2085
2086         return 1;
2087 }
2088
2089 /*!
2090  * \brief Performs a base 64 encode algorithm on the contents of a File
2091  * \param filename The path to the file to be encoded. Must be readable, file is opened in read mode.
2092  * \param so A FILE handle to the output file to receive the base 64 encoded contents of the input file, identified by filename.
2093  *
2094  * 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 ?
2095  *
2096  * \return zero on success, -1 on error.
2097  */
2098 static int base_encode(char *filename, FILE *so)
2099 {
2100         static const unsigned char dtable[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
2101                 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
2102                 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0',
2103                 '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
2104         int i, hiteof = 0;
2105         FILE *fi;
2106         struct baseio bio;
2107
2108         memset(&bio, 0, sizeof(bio));
2109         bio.iocp = BASEMAXINLINE;
2110
2111         if (!(fi = fopen(filename, "rb"))) {
2112                 ast_log(AST_LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno));
2113                 return -1;
2114         }
2115
2116         while (!hiteof) {
2117                 unsigned char igroup[3], ogroup[4];
2118                 int c, n;
2119
2120                 igroup[0] = igroup[1] = igroup[2] = 0;
2121
2122                 for (n = 0; n < 3; n++) {
2123                         if ((c = inchar(&bio, fi)) == EOF) {
2124                                 hiteof = 1;
2125                                 break;
2126                         }
2127
2128                         igroup[n] = (unsigned char)c;
2129                 }
2130
2131                 if (n > 0) {
2132                         ogroup[0] = dtable[igroup[0] >> 2];
2133                         ogroup[1] = dtable[((igroup[0] & 3) << 4) | (igroup[1] >> 4)];
2134                         ogroup[2] = dtable[((igroup[1] & 0xF) << 2) | (igroup[2] >> 6)];
2135                         ogroup[3] = dtable[igroup[2] & 0x3F];
2136
2137                         if (n < 3) {
2138                                 ogroup[3] = '=';
2139
2140                                 if (n < 2)
2141                                         ogroup[2] = '=';
2142                         }
2143
2144                         for (i = 0; i < 4; i++)
2145                                 ochar(&bio, ogroup[i], so);
2146                 }
2147         }
2148
2149         fclose(fi);
2150         
2151         if (fputs(eol, so) == EOF)
2152                 return 0;
2153
2154         return 1;
2155 }
2156
2157 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)
2158 {
2159         char callerid[256];
2160         /* Prepare variables for substitution in email body and subject */
2161         pbx_builtin_setvar_helper(ast, "VM_NAME", vmu->fullname);
2162         pbx_builtin_setvar_helper(ast, "VM_DUR", dur);
2163         snprintf(passdata, passdatasize, "%d", msgnum);
2164         pbx_builtin_setvar_helper(ast, "VM_MSGNUM", passdata);
2165         pbx_builtin_setvar_helper(ast, "VM_CONTEXT", context);
2166         pbx_builtin_setvar_helper(ast, "VM_MAILBOX", mailbox);
2167         pbx_builtin_setvar_helper(ast, "VM_CALLERID", ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, "Unknown Caller"));
2168         pbx_builtin_setvar_helper(ast, "VM_CIDNAME", (cidname ? cidname : "an unknown caller"));
2169         pbx_builtin_setvar_helper(ast, "VM_CIDNUM", (cidnum ? cidnum : "an unknown caller"));
2170         pbx_builtin_setvar_helper(ast, "VM_DATE", date);
2171         pbx_builtin_setvar_helper(ast, "VM_CATEGORY", category ? ast_strdupa(category) : "no category");
2172 }
2173
2174 /*!
2175  * \brief Wraps a character sequence in double quotes, escaping occurences of quotes within the string.
2176  * \param from The string to work with.
2177  * \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.
2178  * 
2179  * \return The destination string with quotes wrapped on it (the to field).
2180  */
2181 static char *quote(const char *from, char *to, size_t len)
2182 {
2183         char *ptr = to;
2184         *ptr++ = '"';
2185         for (; ptr < to + len - 1; from++) {
2186                 if (*from == '"')
2187                         *ptr++ = '\\';
2188                 else if (*from == '\0')
2189                         break;
2190                 *ptr++ = *from;
2191         }
2192         if (ptr < to + len - 1)
2193                 *ptr++ = '"';
2194         *ptr = '\0';
2195         return to;
2196 }
2197
2198 /*! \brief
2199  * fill in *tm for current time according to the proper timezone, if any.
2200  * Return tm so it can be used as a function argument.
2201  */
2202 static const struct ast_tm *vmu_tm(const struct ast_vm_user *vmu, struct ast_tm *tm)
2203 {
2204         const struct vm_zone *z = NULL;
2205         struct timeval t = ast_tvnow();
2206
2207         /* Does this user have a timezone specified? */
2208         if (!ast_strlen_zero(vmu->zonetag)) {
2209                 /* Find the zone in the list */
2210                 AST_LIST_LOCK(&zones);
2211                 AST_LIST_TRAVERSE(&zones, z, list) {
2212                         if (!strcmp(z->name, vmu->zonetag))
2213                                 break;
2214                 }
2215                 AST_LIST_UNLOCK(&zones);
2216         }
2217         ast_localtime(&t, tm, z ? z->timezone : NULL);
2218         return tm;
2219 }
2220
2221 /*! \brief same as mkstemp, but return a FILE * */
2222 static FILE *vm_mkftemp(char *template)
2223 {
2224         FILE *p = NULL;
2225         int pfd = mkstemp(template);
2226         chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
2227         if (pfd > -1) {
2228                 p = fdopen(pfd, "w+");
2229                 if (!p) {
2230                         close(pfd);
2231                         pfd = -1;
2232                 }
2233         }
2234         return p;
2235 }
2236
2237 /*!
2238  * \brief Creates the email file to be sent to indicate a new voicemail exists for a user.
2239  * \param p The output file to generate the email contents into.
2240  * \param srcemail The email address to send the email to, presumably the email address for the owner of the mailbox.
2241  * \param vmu The voicemail user who is sending the voicemail.
2242  * \param msgnum The message index in the mailbox folder.
2243  * \param context 
2244  * \param mailbox The voicemail box to read the voicemail to be notified in this email.
2245  * \param cidnum The caller ID number.
2246  * \param cidname The caller ID name.
2247  * \param attach the name of the sound file to be attached to the email, if attach_user_voicemail == 1.
2248  * \param format The message sound file format. i.e. .wav
2249  * \param duration The time of the message content, in seconds.
2250  * \param attach_user_voicemail if 1, the sound file is attached to the email.
2251  * \param chan
2252  * \param category
2253  * \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.
2254  *
2255  * 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.
2256  */
2257 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)
2258 {
2259         char date[256];
2260         char host[MAXHOSTNAMELEN] = "";
2261         char who[256];
2262         char bound[256];
2263         char dur[256];
2264         struct ast_tm tm;
2265         char *passdata2;
2266         size_t len_passdata;
2267         char *greeting_attachment; 
2268         char filename[256];
2269
2270 #ifdef IMAP_STORAGE
2271 #define ENDL "\r\n"
2272 #else
2273 #define ENDL "\n"
2274 #endif
2275
2276         gethostname(host, sizeof(host) - 1);
2277
2278         if (strchr(srcemail, '@'))
2279                 ast_copy_string(who, srcemail, sizeof(who));
2280         else 
2281                 snprintf(who, sizeof(who), "%s@%s", srcemail, host);
2282         
2283         greeting_attachment = strrchr(ast_strdupa(attach), '/');
2284         if (greeting_attachment)
2285                 *greeting_attachment++ = '\0';
2286
2287         snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
2288         ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
2289         fprintf(p, "Date: %s" ENDL, date);
2290
2291         /* Set date format for voicemail mail */
2292         ast_strftime(date, sizeof(date), emaildateformat, &tm);
2293
2294         if (!ast_strlen_zero(fromstring)) {
2295                 struct ast_channel *ast;
2296                 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2297                         char *passdata;
2298                         int vmlen = strlen(fromstring) * 3 + 200;
2299                         passdata = alloca(vmlen);
2300                         memset(passdata, 0, vmlen);
2301                         prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2302                         pbx_substitute_variables_helper(ast, fromstring, passdata, vmlen);
2303                         len_passdata = strlen(passdata) * 2 + 3;
2304                         passdata2 = alloca(len_passdata);
2305                         fprintf(p, "From: %s <%s>" ENDL, quote(passdata, passdata2, len_passdata), who);
2306                         ast_channel_free(ast);
2307                 } else
2308                         ast_log(AST_LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2309         } else
2310                 fprintf(p, "From: Asterisk PBX <%s>" ENDL, who);
2311         len_passdata = strlen(vmu->fullname) * 2 + 3;
2312         passdata2 = alloca(len_passdata);
2313         fprintf(p, "To: %s <%s>" ENDL, quote(vmu->fullname, passdata2, len_passdata), vmu->email);
2314         if (!ast_strlen_zero(emailsubject)) {
2315                 struct ast_channel *ast;
2316                 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2317                         char *passdata;
2318                         int vmlen = strlen(emailsubject) * 3 + 200;
2319                         passdata = alloca(vmlen);
2320                         memset(passdata, 0, vmlen);
2321                         prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2322                         pbx_substitute_variables_helper(ast, emailsubject, passdata, vmlen);
2323                         fprintf(p, "Subject: %s" ENDL, passdata);
2324                         ast_channel_free(ast);
2325                 } else
2326                         ast_log(AST_LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2327         } else if (ast_test_flag((&globalflags), VM_PBXSKIP))
2328                 fprintf(p, "Subject: New message %d in mailbox %s" ENDL, msgnum + 1, mailbox);
2329         else
2330                 fprintf(p, "Subject: [PBX]: New message %d in mailbox %s" ENDL, msgnum + 1, mailbox);
2331         fprintf(p, "Message-ID: <Asterisk-%d-%d-%s-%d@%s>" ENDL, msgnum + 1, (unsigned int)ast_random(), mailbox, (int)getpid(), host);
2332         if (imap) {
2333                 /* additional information needed for IMAP searching */
2334                 fprintf(p, "X-Asterisk-VM-Message-Num: %d" ENDL, msgnum + 1);
2335                 /* fprintf(p, "X-Asterisk-VM-Orig-Mailbox: %s" ENDL, ext); */
2336                 fprintf(p, "X-Asterisk-VM-Server-Name: %s" ENDL, fromstring);
2337                 fprintf(p, "X-Asterisk-VM-Context: %s" ENDL, context);
2338                 fprintf(p, "X-Asterisk-VM-Extension: %s" ENDL, mailbox);
2339                 fprintf(p, "X-Asterisk-VM-Priority: %d" ENDL, chan->priority);
2340                 fprintf(p, "X-Asterisk-VM-Caller-channel: %s" ENDL, chan->name);
2341                 fprintf(p, "X-Asterisk-VM-Caller-ID-Num: %s" ENDL, cidnum);
2342                 fprintf(p, "X-Asterisk-VM-Caller-ID-Name: %s" ENDL, cidname);
2343                 fprintf(p, "X-Asterisk-VM-Duration: %d" ENDL, duration);
2344                 if (!ast_strlen_zero(category)) 
2345                         fprintf(p, "X-Asterisk-VM-Category: %s" ENDL, category);
2346                 fprintf(p, "X-Asterisk-VM-Message-Type: %s" ENDL, msgnum > -1 ? "Message" : greeting_attachment);
2347                 fprintf(p, "X-Asterisk-VM-Orig-date: %s" ENDL, date);
2348                 fprintf(p, "X-Asterisk-VM-Orig-time: %ld" ENDL, (long)time(NULL));
2349         }
2350         if (!ast_strlen_zero(cidnum))
2351                 fprintf(p, "X-Asterisk-CallerID: %s" ENDL, cidnum);
2352         if (!ast_strlen_zero(cidname))
2353                 fprintf(p, "X-Asterisk-CallerIDName: %s" ENDL, cidname);
2354         fprintf(p, "MIME-Version: 1.0" ENDL);
2355         if (attach_user_voicemail) {
2356                 /* Something unique. */
2357                 snprintf(bound, sizeof(bound), "----voicemail_%d%s%d%d", msgnum + 1, mailbox, (int)getpid(), (unsigned int)ast_random());
2358
2359                 fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"" ENDL, bound);
2360                 fprintf(p, ENDL ENDL "This is a multi-part message in MIME format." ENDL ENDL);
2361                 fprintf(p, "--%s" ENDL, bound);
2362         }
2363         fprintf(p, "Content-Type: text/plain; charset=%s" ENDL "Content-Transfer-Encoding: 8bit" ENDL ENDL, charset);
2364         if (emailbody) {
2365                 struct ast_channel *ast;
2366                 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2367                         char *passdata;
2368                         int vmlen = strlen(emailbody) * 3 + 200;
2369                         passdata = alloca(vmlen);
2370                         memset(passdata, 0, vmlen);
2371                         prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2372                         pbx_substitute_variables_helper(ast, emailbody, passdata, vmlen);
2373                         fprintf(p, "%s" ENDL, passdata);
2374                         ast_channel_free(ast);
2375                 } else
2376                         ast_log(AST_LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2377         } else if (msgnum > -1) {
2378                 fprintf(p, "Dear %s:" ENDL ENDL "\tJust wanted to let you know you were just left a %s long message (number %d)" ENDL
2379
2380                 "in mailbox %s from %s, on %s so you might" ENDL
2381                 "want to check it when you get a chance.  Thanks!" ENDL ENDL "\t\t\t\t--Asterisk" ENDL ENDL, vmu->fullname, 
2382                 dur, msgnum + 1, mailbox, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
2383         } else {
2384                 fprintf(p, "This message is to let you know that your greeting was changed on %s." ENDL
2385                                 "Please do not delete this message, lest your greeting vanish with it." ENDL ENDL, date);
2386         }
2387
2388         if (attach_user_voicemail) {
2389                 if (!ast_strlen_zero(attach2)) {
2390                         snprintf(filename, sizeof(filename), "msgintro%04d.%s", msgnum, format);
2391                         ast_debug(5, "creating attachment filename %s\n", filename);
2392                         add_email_attachment(p, vmu, format, attach2, greeting_attachment, mailbox, bound, filename, 0, msgnum);
2393                         snprintf(filename, sizeof(filename), "msg%04d.%s", msgnum, format);
2394                         ast_debug(5, "creating second attachment filename %s\n", filename);
2395                         add_email_attachment(p, vmu, format, attach, greeting_attachment, mailbox, bound, filename, 1, msgnum);
2396                 } else {
2397                         snprintf(filename, sizeof(filename), "msg%04d.%s", msgnum, format);
2398                         ast_debug(5, "creating attachment filename %s, no second attachment.\n", filename);
2399                         add_email_attachment(p, vmu, format, attach, greeting_attachment, mailbox, bound, filename, 1, msgnum);
2400                 }
2401         }
2402 }
2403
2404 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)
2405 {
2406         char tmpdir[256], newtmp[256];
2407         char fname[256];
2408         char tmpcmd[256];
2409         int tmpfd = -1;
2410
2411         /* Eww. We want formats to tell us their own MIME type */
2412         char *ctype = (!strcasecmp(format, "ogg")) ? "application/" : "audio/x-";
2413
2414         if (vmu->volgain < -.001 || vmu->volgain > .001) {
2415                 create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, vmu->mailbox, "tmp");
2416                 snprintf(newtmp, sizeof(newtmp), "%s/XXXXXX", tmpdir);
2417                 tmpfd = mkstemp(newtmp);
2418                 chmod(newtmp, VOICEMAIL_FILE_MODE & ~my_umask);
2419                 ast_debug(3, "newtmp: %s\n", newtmp);
2420                 if (tmpfd > -1) {
2421                         snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, attach, format, newtmp, format);
2422                         ast_safe_system(tmpcmd);
2423                         attach = newtmp;
2424                         ast_debug(3, "VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", attach, format, vmu->volgain, mailbox);
2425                 }
2426         }
2427         fprintf(p, "--%s" ENDL, bound);
2428         if (msgnum > -1)
2429                 fprintf(p, "Content-Type: %s%s; name=\"%s\"" ENDL, ctype, format, filename);
2430         else
2431                 fprintf(p, "Content-Type: %s%s; name=\"%s.%s\"" ENDL, ctype, format, attach, format);
2432         fprintf(p, "Content-Transfer-Encoding: base64" ENDL);
2433         fprintf(p, "Content-Description: Voicemail sound attachment." ENDL);
2434         if (msgnum > -1)
2435                 fprintf(p, "Content-Disposition: attachment; filename=\"%s\"" ENDL ENDL, filename);
2436         else
2437                 fprintf(p, "Content-Disposition: attachment; filename=\"%s.%s\"" ENDL ENDL, attach, format);
2438         snprintf(fname, sizeof(fname), "%s.%s", attach, format);
2439         base_encode(fname, p);
2440         if (last)
2441                 fprintf(p, ENDL ENDL "--%s--" ENDL "." ENDL, bound);
2442         if (tmpfd > -1) {
2443                 unlink(fname);
2444                 close(tmpfd);
2445                 unlink(newtmp);
2446         }
2447         return 0;
2448 }
2449 #undef ENDL
2450
2451 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)
2452 {
2453         FILE *p = NULL;
2454         char tmp[80] = "/tmp/astmail-XXXXXX";
2455         char tmp2[256];
2456
2457         if (vmu && ast_strlen_zero(vmu->email)) {
2458                 ast_log(AST_LOG_WARNING, "E-mail address missing for mailbox [%s].  E-mail will not be sent.\n", vmu->mailbox);
2459                 return(0);
2460         }
2461         if (!strcmp(format, "wav49"))
2462                 format = "WAV";
2463         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));
2464         /* Make a temporary file instead of piping directly to sendmail, in case the mail
2465            command hangs */
2466         if ((p = vm_mkftemp(tmp)) == NULL) {
2467                 ast_log(AST_LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
2468                 return -1;
2469         } else {
2470                 make_email_file(p, srcemail, vmu, msgnum, context, mailbox, cidnum, cidname, attach, attach2, format, duration, attach_user_voicemail, chan, category, 0);
2471                 fclose(p);
2472                 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
2473                 ast_safe_system(tmp2);
2474                 ast_debug(1, "Sent mail to %s with command '%s'\n", vmu->email, mailcmd);
2475         }
2476         return 0;
2477 }
2478
2479 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)
2480 {
2481         char date[256];
2482         char host[MAXHOSTNAMELEN] = "";
2483         char who[256];
2484         char dur[PATH_MAX];
2485         char tmp[80] = "/tmp/astmail-XXXXXX";
2486         char tmp2[PATH_MAX];
2487         struct ast_tm tm;
2488         FILE *p;
2489
2490         if ((p = vm_mkftemp(tmp)) == NULL) {
2491                 ast_log(AST_LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
2492                 return -1;
2493         }
2494         gethostname(host, sizeof(host) - 1);
2495         if (strchr(srcemail, '@'))
2496                 ast_copy_string(who, srcemail, sizeof(who));
2497         else 
2498                 snprintf(who, sizeof(who), "%s@%s", srcemail, host);
2499         snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
2500         ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
2501         fprintf(p, "Date: %s\n", date);
2502
2503         if (*pagerfromstring) {
2504                 struct ast_channel *ast;
2505                 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2506                         char *passdata;
2507                         int vmlen = strlen(fromstring) * 3 + 200;
2508                         passdata = alloca(vmlen);
2509                         memset(passdata, 0, vmlen);
2510                         prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2511                         pbx_substitute_variables_helper(ast, pagerfromstring, passdata, vmlen);
2512                         fprintf(p, "From: %s <%s>\n", passdata, who);
2513                         ast_channel_free(ast);
2514                 } else 
2515                         ast_log(AST_LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2516         } else
2517                 fprintf(p, "From: Asterisk PBX <%s>\n", who);
2518         fprintf(p, "To: %s\n", pager);
2519         if (pagersubject) {
2520                 struct ast_channel *ast;
2521                 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2522                         char *passdata;
2523                         int vmlen = strlen(pagersubject) * 3 + 200;
2524                         passdata = alloca(vmlen);
2525                         memset(passdata, 0, vmlen);
2526                         prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2527                         pbx_substitute_variables_helper(ast, pagersubject, passdata, vmlen);
2528                         fprintf(p, "Subject: %s\n\n", passdata);
2529                         ast_channel_free(ast);
2530                 } else
2531                         ast_log(AST_LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2532         } else
2533                 fprintf(p, "Subject: New VM\n\n");
2534
2535         ast_strftime(date, sizeof(date), "%A, %B %d, %Y at %r", &tm);
2536         if (pagerbody) {
2537                 struct ast_channel *ast;
2538                 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2539                         char *passdata;
2540                         int vmlen = strlen(pagerbody) * 3 + 200;
2541                         passdata = alloca(vmlen);
2542                         memset(passdata, 0, vmlen);
2543                         prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2544                         pbx_substitute_variables_helper(ast, pagerbody, passdata, vmlen);
2545                         fprintf(p, "%s\n", passdata);
2546                         ast_channel_free(ast);
2547                 } else
2548                         ast_log(AST_LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2549         } else {
2550                 fprintf(p, "New %s long msg in box %s\n"
2551                                 "from %s, on %s", dur, mailbox, (cidname ? cidname : (cidnum ? cidnum : "unknown")), date);
2552         }
2553         fclose(p);
2554         snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
2555         ast_safe_system(tmp2);
2556         ast_debug(1, "Sent page to %s with command '%s'\n", pager, mailcmd);
2557         return 0;
2558 }
2559
2560 /*!
2561  * \brief Gets the current date and time, as formatted string.
2562  * \param s The buffer to hold the output formatted date.
2563  * \param len the length of the buffer. Used to prevent buffer overflow in ast_strftime.
2564  * 
2565  * The date format string used is "%a %b %e %r UTC %Y".
2566  * 
2567  * \return zero on success, -1 on error.
2568  */
2569 static int get_date(char *s, int len)
2570 {
2571         struct ast_tm tm;
2572         struct timeval t = ast_tvnow();
2573         
2574         ast_localtime(&t, &tm, "UTC");
2575
2576         return ast_strftime(s, len, "%a %b %e %r UTC %Y", &tm);
2577 }
2578
2579 static int play_greeting(struct ast_channel *chan, struct ast_vm_user *vmu, char *filename, char *ecodes)
2580 {
2581         int res = -2;
2582         
2583 #ifdef ODBC_STORAGE
2584         int success = 
2585 #endif
2586         RETRIEVE(filename, -1, vmu->mailbox, vmu->context);
2587         if (ast_fileexists(filename, NULL, NULL) > 0) {
2588                 res = ast_streamfile(chan, filename, chan->language);
2589                 if (res > -1) 
2590                         res = ast_waitstream(chan, ecodes);
2591 #ifdef ODBC_STORAGE
2592                 if (success == -1) {
2593                         /* We couldn't retrieve the file from the database, but we found it on the file system. Let's put it in the database. */
2594                         ast_debug(1, "Greeting not retrieved from database, but found in file storage. Inserting into database\n");
2595                         store_file(filename, vmu->mailbox, vmu->context, -1);
2596                 }
2597 #endif
2598         }
2599         DISPOSE(filename, -1);
2600
2601         return res;
2602 }
2603
2604 static int invent_message(struct ast_channel *chan, struct ast_vm_user *vmu, char *ext, int busy, char *ecodes)
2605 {
2606         int res;
2607         char fn[PATH_MAX];
2608         char dest[PATH_MAX];
2609
2610         snprintf(fn, sizeof(fn), "%s%s/%s/greet", VM_SPOOL_DIR, vmu->context, ext);
2611
2612         if ((res = create_dirpath(dest, sizeof(dest), vmu->context, ext, ""))) {
2613                 ast_log(AST_LOG_WARNING, "Failed to make directory(%s)\n", fn);
2614                 return -1;
2615         }
2616
2617         res = play_greeting(chan, vmu, fn, ecodes);
2618         if (res == -2) {
2619                 /* File did not exist */
2620                 res = ast_stream_and_wait(chan, "vm-theperson", ecodes);
2621                 if (res)
2622                         return res;
2623                 res = ast_say_digit_str(chan, ext, ecodes, chan->language);
2624         }
2625         if (res)
2626                 return res;
2627
2628         res = ast_stream_and_wait(chan, busy ? "vm-isonphone" : "vm-isunavail", ecodes);
2629         return res;
2630 }
2631
2632 static void free_user(struct ast_vm_user *vmu)
2633 {
2634         if (!ast_test_flag(vmu, VM_ALLOCED))
2635                 return;
2636
2637         ast_free(vmu);
2638 }
2639
2640 static void free_zone(struct vm_zone *z)
2641 {
2642         ast_free(z);
2643 }
2644
2645 /*!
2646  * \brief Gets the name of the mailbox folder from the numeric index.
2647  * \param id The numerical index for the folder name.
2648  * 
2649  * When an invalid number is entered, or one that exceeds the pre-configured list of folder names, the name "tmp" is returned.
2650  *
2651  * \return the String name that coresponds to this folder index.
2652  */
2653 static const char *mbox(int id)
2654 {
2655         static const char *msgs[] = {
2656 #ifdef IMAP_STORAGE
2657                 imapfolder,
2658 #else
2659                 "INBOX",
2660 #endif
2661                 "Old",
2662                 "Work",
2663                 "Family",
2664                 "Friends",
2665                 "Cust1",
2666                 "Cust2",
2667                 "Cust3",
2668                 "Cust4",
2669                 "Cust5",
2670                 "Deleted",
2671         };
2672         return (id >= 0 && id < (sizeof(msgs) / sizeof(msgs[0]))) ? msgs[id] : "tmp";
2673 }
2674 #ifdef IMAP_STORAGE
2675 /*!
2676  * \brief Converts a string folder name into the numerical identifier.
2677  * \param folder the string folder name to be converted to an id.
2678  *
2679  * This is the opposite of the mbox() function.
2680  *
2681  * \return the id that coresponds to the folder name
2682  */
2683 static int folder_int(const char *folder)
2684 {
2685         /* assume a NULL folder means INBOX */
2686         if (!folder)
2687                 return 0;
2688 #ifdef IMAP_STORAGE
2689         if (!strcasecmp(folder, imapfolder))
2690 #else
2691         if (!strcasecmp(folder, "INBOX"))
2692 #endif
2693                 return 0;
2694         else if (!strcasecmp(folder, "Old"))
2695                 return 1;
2696         else if (!strcasecmp(folder, "Work"))
2697                 return 2;
2698         else if (!strcasecmp(folder, "Family"))
2699                 return 3;
2700         else if (!strcasecmp(folder, "Friends"))
2701                 return 4;
2702         else if (!strcasecmp(folder, "Cust1"))
2703                 return 5;
2704         else if (!strcasecmp(folder, "Cust2"))
2705                 return 6;
2706         else if (!strcasecmp(folder, "Cust3"))
2707                 return 7;
2708         else if (!strcasecmp(folder, "Cust4"))
2709                 return 8;
2710         else if (!strcasecmp(folder, "Cust5"))
2711                 return 9;
2712         else if (!strcasecmp(folder, "Deleted"))
2713                 return 10;
2714         else /* assume they meant INBOX if folder is not found otherwise */
2715                 return 0;
2716 }
2717 #endif
2718
2719 #ifdef ODBC_STORAGE
2720 /*! XXX \todo Fix this function to support multiple mailboxes in the intput string */
2721 static int inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
2722 {
2723         int x = -1;
2724         int res;
2725         SQLHSTMT stmt;
2726         char sql[PATH_MAX];
2727         char rowdata[20];
2728         char tmp[PATH_MAX] = "";
2729         struct odbc_obj *obj;
2730         char *context;
2731         struct generic_prepare_struct gps = { .sql = sql, .argc = 0 };
2732
2733         if (newmsgs)
2734                 *newmsgs = 0;
2735         if (oldmsgs)
2736                 *oldmsgs = 0;
2737
2738         /* If no mailbox, return immediately */
2739         if (ast_strlen_zero(mailbox))
2740                 return 0;
2741
2742         ast_copy_string(tmp, mailbox, sizeof(tmp));
2743         
2744         context = strchr(tmp, '@');
2745         if (context) {
2746                 *context = '\0';
2747                 context++;
2748         } else
2749                 context = "default";
2750         
2751         obj = ast_odbc_request_obj(odbc_database, 0);
2752         if (obj) {
2753                 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "INBOX");
2754                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2755                 if (!stmt) {
2756                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2757                         ast_odbc_release_obj(obj);
2758                         goto yuck;
2759                 }
2760                 res = SQLFetch(stmt);
2761                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2762                         ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2763                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2764                         ast_odbc_release_obj(obj);
2765                         goto yuck;
2766                 }
2767                 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2768                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2769                         ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2770                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2771                         ast_odbc_release_obj(obj);
2772                         goto yuck;
2773                 }
2774                 *newmsgs = atoi(rowdata);
2775                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2776
2777                 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "Old");
2778                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2779                 if (!stmt) {
2780                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2781                         ast_odbc_release_obj(obj);
2782                         goto yuck;
2783                 }
2784                 res = SQLFetch(stmt);
2785                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2786                         ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2787                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2788                         ast_odbc_release_obj(obj);
2789                         goto yuck;
2790                 }
2791                 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2792                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2793                         ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2794                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2795                         ast_odbc_release_obj(obj);
2796                         goto yuck;
2797                 }
2798                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2799                 ast_odbc_release_obj(obj);
2800                 *oldmsgs = atoi(rowdata);
2801                 x = 0;
2802         } else
2803                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2804                 
2805 yuck:   
2806         return x;
2807 }
2808
2809 /*!
2810  * \brief Gets the number of messages that exist in a mailbox folder.
2811  * \param context
2812  * \param mailbox
2813  * \param folder
2814  * 
2815  * This method is used when ODBC backend is used.
2816  * \return The number of messages in this mailbox folder (zero or more).
2817  */
2818 static int messagecount(const char *context, const char *mailbox, const char *folder)
2819 {
2820         struct odbc_obj *obj = NULL;
2821         int nummsgs = 0;
2822         int res;
2823         SQLHSTMT stmt = NULL;
2824         char sql[PATH_MAX];
2825         char rowdata[20];
2826         struct generic_prepare_struct gps = { .sql = sql, .argc = 0 };
2827         if (!folder)
2828                 folder = "INBOX";
2829         /* If no mailbox, return immediately */
2830         if (ast_strlen_zero(mailbox))
2831                 return 0;
2832
2833         obj = ast_odbc_request_obj(odbc_database, 0);
2834         if (obj) {
2835                 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, mailbox, folder);
2836                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2837                 if (!stmt) {
2838                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2839                         goto yuck;
2840                 }
2841                 res = SQLFetch(stmt);
2842                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2843                         ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2844                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2845                         goto yuck;
2846                 }
2847                 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2848                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2849                         ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2850                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2851                         goto yuck;
2852                 }
2853                 nummsgs = atoi(rowdata);
2854                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2855         } else
2856                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2857
2858 yuck:
2859         if (obj)
2860                 ast_odbc_release_obj(obj);
2861         return nummsgs;
2862 }
2863
2864 /** 
2865  * \brief Determines if the given folder has messages.
2866  * \param mailbox The @ delimited string for user@context. If no context is found, uses 'default' for the context.
2867  * 
2868  * This function is used when the mailbox is stored in an ODBC back end.
2869  * This invokes the messagecount(). Here we are interested in the presence of messages (> 0) only, not the actual count.
2870  * \return 1 if the folder has one or more messages. zero otherwise.
2871  */
2872 static int has_voicemail(const char *mailbox, const char *folder)
2873 {
2874         char tmp[256], *tmp2 = tmp, *mbox, *context;
2875         ast_copy_string(tmp, mailbox, sizeof(tmp));
2876         while ((context = mbox = strsep(&tmp2, ","))) {
2877                 strsep(&context, "@");
2878                 if (ast_strlen_zero(context))
2879                         context = "default";
2880                 if (messagecount(context, mbox, folder))
2881                         return 1;
2882         }
2883         return 0;
2884 }
2885
2886 #elif defined(IMAP_STORAGE)
2887
2888 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)
2889 {
2890         char *myserveremail = serveremail;
2891         char fn[PATH_MAX];
2892         char intro[PATH_MAX];
2893         char mailbox[256];
2894         char *stringp;
2895         FILE *p = NULL;
2896         char tmp[80] = "/tmp/astmail-XXXXXX";
2897         long len;
2898         void *buf;
2899         int tempcopy = 0;
2900         int ret;
2901         STRING str;
2902         
2903         /* Attach only the first format */
2904         fmt = ast_strdupa(fmt);
2905         stringp = fmt;
2906         strsep(&stringp, "|");
2907
2908         if (!ast_strlen_zero(vmu->serveremail))
2909                 myserveremail = vmu->serveremail;
2910
2911         if (msgnum > -1)
2912                 make_file(fn, sizeof(fn), dir, msgnum);
2913         else
2914                 ast_copy_string (fn, dir, sizeof(fn));
2915         
2916         if (ast_strlen_zero(vmu->email)) {
2917                 /* We need the vmu->email to be set when we call make_email_file, but
2918                  * if we keep it set, a duplicate e-mail will be created. So at the end
2919                  * of this function, we will revert back to an empty string if tempcopy
2920                  * is 1.
2921                  */
2922                 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
2923                 tempcopy = 1;
2924         }
2925
2926         if (!ast_strlen_zero(introfile)) {
2927                 snprintf(intro, sizeof(intro), "%s/msgintro%04d", dir, msgnum);
2928         } else {
2929                 intro[0] = '\0';
2930         }
2931
2932         if (!strcmp(fmt, "wav49"))
2933                 fmt = "WAV";
2934         ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
2935
2936         /* Make a temporary file instead of piping directly to sendmail, in case the mail
2937            command hangs. */
2938         if (!(p = vm_mkftemp(tmp))) {
2939                 ast_log(AST_LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
2940                 if (tempcopy)
2941                         *(vmu->email) = '\0';
2942                 return -1;
2943         }
2944
2945         if (msgnum < 0 && imapgreetings) {
2946                 if ((ret = init_mailstream(vms, GREETINGS_FOLDER))) {
2947                         ast_log(AST_LOG_WARNING, "Unable to open mailstream.\n");
2948                         return -1;
2949                 }
2950                 imap_delete_old_greeting(fn, vms);
2951         }
2952         
2953         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);
2954         /* read mail file to memory */          
2955         len = ftell(p);
2956         rewind(p);
2957         if (!(buf = ast_malloc(len + 1))) {
2958                 ast_log(AST_LOG_ERROR, "Can't allocate %ld bytes to read message\n", len + 1);
2959                 fclose(p);
2960                 if (tempcopy)
2961                         *(vmu->email) = '\0';
2962                 return -1;
2963         }
2964         fread(buf, len, 1, p);
2965         ((char *)buf)[len] = '\0';
2966         INIT(&str, mail_string, buf, len);
2967         if ((ret = init_mailstream(vms, NEW_FOLDER))) {
2968                 ast_log(AST_LOG_WARNING, "Unable to open mailstream.\n");
2969                 return -1;
2970         }
2971         
2972         imap_mailbox_name(mailbox, sizeof(mailbox), vms, NEW_FOLDER, 1);
2973         if (!mail_append(vms->mailstream, mailbox, &str))
2974                 ast_log(AST_LOG_ERROR, "Error while sending the message to %s\n", mailbox);
2975         fclose(p);
2976         unlink(tmp);
2977         ast_free(buf);
2978         ast_debug(3, "%s stored\n", fn);
2979         
2980         if (tempcopy)
2981                 *(vmu->email) = '\0';
2982         
2983         return 0;
2984
2985 }
2986
2987 /*!
2988  * \brief Gets the number of messages that exist in a mailbox folder.
2989  * \param context
2990  * \param mailbox
2991  * \param folder
2992  * 
2993  * This method is used when IMAP backend is used.
2994  * \return The number of messages in this mailbox folder (zero or more).
2995  */
2996 static int messagecount(const char *context, const char *mailbox, const char *folder)
2997 {
2998         SEARCHPGM *pgm;
2999         SEARCHHEADER *hdr;
3000
3001         struct ast_vm_user *vmu, vmus;
3002         struct vm_state *vms_p;
3003         int ret = 0;
3004         int fold = folder_int(folder);
3005         
3006         if (ast_strlen_zero(mailbox))
3007                 return 0;
3008
3009         /* We have to get the user before we can open the stream! */
3010         /* ast_log(AST_LOG_DEBUG, "Before find_user, context is %s and mailbox is %s\n", context, mailbox); */
3011         vmu = find_user(&vmus, context, mailbox);
3012         if (!vmu) {
3013                 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailbox, context);
3014                 return -1;
3015         } else {
3016                 /* No IMAP account available */
3017                 if (vmu->imapuser[0] == '\0') {
3018                         ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
3019                         return -1;
3020                 }
3021         }
3022         
3023         /* No IMAP account available */
3024         if (vmu->imapuser[0] == '\0') {
3025                 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
3026                 free_user(vmu);
3027                 return -1;
3028         }
3029
3030         /* check if someone is accessing this box right now... */
3031         vms_p = get_vm_state_by_imapuser(vmu->imapuser, 1);
3032         if (!vms_p) {
3033                 vms_p = get_vm_state_by_mailbox(mailbox, 1);
3034         }
3035         if (vms_p) {
3036                 ast_debug(3, "Returning before search - user is logged in\n");
3037                 if (fold == 0) { /* INBOX */
3038                         return vms_p->newmessages;
3039                 }
3040                 if (fold == 1) { /* Old messages */
3041                         return vms_p->oldmessages;
3042                 }
3043         }
3044
3045         /* add one if not there... */
3046         vms_p = get_vm_state_by_imapuser(vmu->imapuser, 0);
3047         if (!vms_p) {
3048                 vms_p = get_vm_state_by_mailbox(mailbox, 0);
3049         }
3050
3051         if (!vms_p) {
3052                 ast_debug(3, "Adding new vmstate for %s\n", vmu->imapuser);
3053                 if (!(vms_p = ast_calloc(1, sizeof(*vms_p)))) {
3054                         return -1;
3055                 }
3056                 ast_copy_string(vms_p->imapuser, vmu->imapuser, sizeof(vms_p->imapuser));
3057                 ast_copy_string(vms_p->username, mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
3058                 vms_p->mailstream = NIL; /* save for access from interactive entry point */
3059                 ast_debug(3, "Copied %s to %s\n", vmu->imapuser, vms_p->imapuser);
3060                 vms_p->updated = 1;
3061                 /* set mailbox to INBOX! */
3062                 ast_copy_string(vms_p->curbox, mbox(fold), sizeof(vms_p->curbox));
3063                 init_vm_state(vms_p);
3064                 vmstate_insert(vms_p);
3065         }
3066         ret = init_mailstream(vms_p, fold);
3067         if (!vms_p->mailstream) {
3068                 ast_log(AST_LOG_ERROR, "Houston we have a problem - IMAP mailstream is NULL\n");
3069                 return -1;
3070         }
3071         if (ret == 0) {
3072                 pgm = mail_newsearchpgm ();
3073                 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)mailbox);
3074                 pgm->header = hdr;
3075                 if (fold != 1) {
3076                         pgm->unseen = 1;
3077                         pgm->seen = 0;
3078                 }
3079                 /* In the special case where fold is 1 (old messages) we have to do things a bit
3080                  * differently. Old messages are stored in the INBOX but are marked as "seen"
3081                  */
3082                 else {
3083                         pgm->unseen = 0;
3084                         pgm->seen = 1;
3085                 }
3086                 pgm->undeleted = 1;
3087                 pgm->deleted = 0;
3088
3089                 vms_p->vmArrayIndex = 0;
3090                 mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
3091                 if (fold == 0)
3092                         vms_p->newmessages = vms_p->vmArrayIndex;
3093                 if (fold == 1)
3094                         vms_p->oldmessages = vms_p->vmArrayIndex;
3095                 /* Freeing the searchpgm also frees the searchhdr */
3096                 mail_free_searchpgm(&pgm);
3097                 vms_p->updated = 0;
3098                 return vms_p->vmArrayIndex;
3099         } else {  
3100                 mail_ping(vms_p->mailstream);
3101         }
3102         return 0;
3103 }
3104
3105 /*!
3106  * \brief Gets the number of messages that exist in the inbox folder.
3107  * \param mailbox_context
3108  * \param newmsgs The variable that is updated with the count of new messages within this inbox.
3109  * \param oldmsgs The variable that is updated with the count of old messages within this inbox.
3110  * 
3111  * This method is used when IMAP backend is used.
3112  * Simultaneously determines the count of new and old messages. The total messages would then be the sum of these two.
3113  *
3114  * \return zero on success, -1 on error.
3115  */
3116 static int inboxcount(const char *mailbox_context, int *newmsgs, int *oldmsgs)
3117 {
3118         char tmp[PATH_MAX] = "";
3119         char *mailboxnc;
3120         char *context;
3121         char *mb;
3122         char *cur;
3123         if (newmsgs)
3124                 *newmsgs = 0;
3125         if (oldmsgs)
3126                 *oldmsgs = 0;
3127
3128         ast_debug(3, "Mailbox is set to %s\n", mailbox_context);
3129         /* If no mailbox, return immediately */
3130         if (ast_strlen_zero(mailbox_context))
3131                 return 0;
3132         
3133         ast_copy_string(tmp, mailbox_context, sizeof(tmp));
3134         context = strchr(tmp, '@');
3135         if (strchr(mailbox_context, ',')) {
3136                 int tmpnew, tmpold;
3137                 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
3138                 mb = tmp;
3139                 while ((cur = strsep(&mb, ", "))) {
3140                         if (!ast_strlen_zero(cur)) {
3141                                 if (inboxcount(cur, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
3142                                         return -1;
3143                                 else {
3144                                         if (newmsgs)
3145                                                 *newmsgs += tmpnew; 
3146                                         if (oldmsgs)
3147                                                 *oldmsgs += tmpold;
3148                                 }
3149                         }
3150                 }
3151                 return 0;
3152         }
3153         if (context) {
3154                 *context = '\0';
3155                 mailboxnc = tmp;
3156                 context++;
3157         } else {
3158                 context = "default";
3159                 mailboxnc = (char *)mailbox_context;
3160         }
3161         if (newmsgs) {
3162                 if ((*newmsgs = messagecount(context, mailboxnc, imapfolder)) < 0)
3163                         return -1;
3164         }
3165         if (oldmsgs) {
3166                 if ((*oldmsgs = messagecount(context, mailboxnc, "Old")) < 0)
3167                         return -1;
3168         }
3169         return 0;
3170 }
3171         
3172
3173 /** 
3174  * \brief Determines if the given folder has messages.
3175  * \param mailbox The @ delimited string for user@context. If no context is found, uses 'default' for the context.
3176  * \param folder the folder to look in
3177  *
3178  * This function is used when the mailbox is stored in an IMAP back end.
3179  * This invokes the messagecount(). Here we are interested in the presence of messages (> 0) only, not the actual count.
3180  * \return 1 if the folder has one or more messages. zero otherwise.
3181  */
3182 static int has_voicemail(const char *mailbox, const char *folder)
3183 {
3184         char tmp[256], *tmp2, *mbox, *context;
3185         ast_copy_string(tmp, mailbox, sizeof(tmp));
3186         tmp2 = tmp;
3187         if (strchr(tmp2, ',')) {
3188                 while ((mbox = strsep(&tmp2, ","))) {
3189                         if (!ast_strlen_zero(mbox)) {
3190                                 if (has_voicemail(mbox, folder))
3191                                         return 1;
3192                         }
3193                 }
3194         }
3195         if ((context= strchr(tmp, '@')))
3196                 *context++ = '\0';
3197         else
3198                 context = "default";
3199         return messagecount(context, tmp, folder) ? 1 : 0;
3200 }
3201
3202 /*!
3203  * \brief Copies a message from one mailbox to another.
3204  * \param chan
3205  * \param vmu
3206  * \param imbox
3207  * \param msgnum
3208  * \param duration
3209  * \param recip
3210  * \param fmt
3211  * \param dir
3212  *
3213  * This works with IMAP storage based mailboxes.
3214  *
3215  * \return zero on success, -1 on error.
3216  */
3217 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)
3218 {
3219         struct vm_state *sendvms = NULL, *destvms = NULL;
3220         char messagestring[10]; /* I guess this could be a problem if someone has more than 999,999,999 messages... */
3221         if (!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 0))) {
3222                 ast_log(AST_LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
3223                 return -1;
3224         }
3225         if (!(destvms = get_vm_state_by_imapuser(recip->imapuser, 0))) {
3226                 ast_log(AST_LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
3227                 return -1;
3228         }
3229         snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
3230         if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(imbox)) == T))
3231                 return 0;
3232         ast_log(AST_LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
3233         return -1;
3234 }
3235
3236 #endif
3237 #ifndef IMAP_STORAGE
3238 /*! 
3239  * \brief Copies a message from one mailbox to another.
3240  * \param chan
3241  * \param vmu
3242  * \param imbox
3243  * \param msgnum
3244  * \param duration
3245  * \param recip
3246  * \param fmt
3247  * \param dir
3248  *
3249  * This is only used by file storage based mailboxes.
3250  *
3251  * \return zero on success, -1 on error.
3252  */
3253 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)
3254 {
3255         char fromdir[PATH_MAX], todir[PATH_MAX], frompath[PATH_MAX], topath[PATH_MAX];
3256         const char *frombox = mbox(imbox);
3257         int recipmsgnum;
3258
3259         ast_log(AST_LOG_NOTICE, "Copying message from %s@%s to %s@%s\n", vmu->mailbox, vmu->context, recip->mailbox, recip->context);
3260
3261         create_dirpath(todir, sizeof(todir), recip->context, recip->mailbox, "INBOX");
3262         
3263         if (!dir)
3264                 make_dir(fromdir, sizeof(fromdir), vmu->context, vmu->mailbox, frombox);
3265         else
3266                 ast_copy_string(fromdir, dir, sizeof(fromdir));
3267
3268         make_file(frompath, sizeof(frompath), fromdir, msgnum);
3269         make_dir(todir, sizeof(todir), recip->context, recip->mailbox, "INBOX");
3270
3271         if (vm_lock_path(todir))
3272                 return ERROR_LOCK_PATH;
3273
3274         recipmsgnum = last_message_index(recip, todir) + 1;
3275         if (recipmsgnum < recip->maxmsg) {
3276                 make_file(topath, sizeof(topath), todir, recipmsgnum);
3277                 COPY(fromdir, msgnum, todir, recipmsgnum, recip->mailbox, recip->context, frompath, topath);
3278         } else {
3279                 ast_log(AST_LOG_ERROR, "Recipient mailbox %s@%s is full\n", recip->mailbox, recip->context);
3280         }
3281         ast_unlock_path(todir);
3282         notify_new_message(chan, recip, NULL, recipmsgnum, duration, fmt, S_OR(chan->cid.cid_num, NULL), S_OR(chan->cid.cid_name, NULL));
3283         
3284         return 0;
3285 }
3286 #endif
3287 #if !(defined(IMAP_STORAGE) || defined(ODBC_STORAGE))
3288
3289 static int messagecount(const char *context, const char *mailbox, const char *folder)
3290 {
3291         return __has_voicemail(context, mailbox, folder, 0);
3292 }
3293
3294
3295 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit)
3296 {
3297         DIR *dir;
3298         struct dirent *de;
3299         char fn[256];
3300         int ret = 0;
3301
3302         /* If no mailbox, return immediately */
3303         if (ast_strlen_zero(mailbox))
3304                 return 0;
3305
3306         if (ast_strlen_zero(folder))
3307                 folder = "INBOX";
3308         if (ast_strlen_zero(context))
3309                 context = "default";
3310
3311         snprintf(fn, sizeof(fn), "%s%s/%s/%s", VM_SPOOL_DIR, context, mailbox, folder);
3312
3313         if (!(dir = opendir(fn)))
3314                 return 0;
3315
3316         while ((de = readdir(dir))) {
3317                 if (!strncasecmp(de->d_name, "msg", 3)) {
3318                         if (shortcircuit) {
3319                                 ret = 1;
3320                                 break;
3321                         } else if (!strncasecmp(de->d_name + 8, "txt", 3))
3322                                 ret++;
3323                 }
3324         }
3325
3326         closedir(dir);
3327
3328         return ret;
3329 }
3330
3331
3332 /** 
3333  * \brief Determines if the given folder has messages.
3334  * \param mailbox The @ delimited string for user@context. If no context is found, uses 'default' for the context.
3335  * \param folder the folder to look in
3336  *
3337  * This function is used when the mailbox is stored in a filesystem back end.
3338  * This invokes the messagecount(). Here we are interested in the presence of messages (> 0) only, not the actual count.
3339  * \return 1 if the folder has one or more messages. zero otherwise.
3340  */
3341 static int has_voicemail(const char *mailbox, const char *folder)
3342 {
3343         char tmp[256], *tmp2 = tmp, *mbox, *context;
3344         ast_copy_string(tmp, mailbox, sizeof(tmp));
3345         while ((mbox = strsep(&tmp2, ","))) {
3346                 if ((context = strchr(mbox, '@')))
3347                         *context++ = '\0';
3348                 else
3349                         context = "default";
3350                 if (__has_voicemail(context, mbox, folder, 1))
3351                         return 1;
3352         }
3353         return 0;
3354 }
3355
3356
3357 static int inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
3358 {
3359         char tmp[256];
3360         char *context;
3361
3362         /* If no mailbox, return immediately */
3363         if (ast_strlen_zero(mailbox))
3364                 return 0;
3365
3366         if (newmsgs)
3367                 *newmsgs = 0;
3368         if (oldmsgs)
3369                 *oldmsgs = 0;
3370
3371         if (strchr(mailbox, ',')) {
3372                 int tmpnew, tmpold;
3373                 char *mb, *cur;
3374
3375                 ast_copy_string(tmp, mailbox, sizeof(tmp));
3376                 mb = tmp;
3377                 while ((cur = strsep(&mb, ", "))) {
3378                         if (!ast_strlen_zero(cur)) {
3379                                 if (inboxcount(cur, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
3380                                         return -1;
3381                                 else {
3382                                         if (newmsgs)
3383                                                 *newmsgs += tmpnew; 
3384                                         if (oldmsgs)
3385                                                 *oldmsgs += tmpold;
3386                                 }
3387                         }
3388                 }
3389                 return 0;
3390         }
3391
3392         ast_copy_string(tmp, mailbox, sizeof(tmp));
3393         
3394         if ((context = strchr(tmp, '@')))
3395                 *context++ = '\0';
3396         else
3397                 context = "default";
3398
3399         if (newmsgs)
3400                 *newmsgs = __has_voicemail(context, tmp, "INBOX", 0);
3401         if (oldmsgs)
3402                 *oldmsgs = __has_voicemail(context, tmp, "Old", 0);
3403
3404         return 0;
3405 }
3406
3407 #endif
3408
3409 static void run_externnotify(char *context, char *extension)
3410 {
3411         char arguments[255];
3412         char ext_context[256] = "";
3413         int newvoicemails = 0, oldvoicemails = 0;
3414         struct ast_smdi_mwi_message *mwi_msg;
3415
3416         if (!ast_strlen_zero(context))
3417                 snprintf(ext_context, sizeof(ext_context), "%s@%s", extension, context);
3418         else
3419                 ast_copy_string(ext_context, extension, sizeof(ext_context));
3420
3421         if (smdi_iface) {
3422                 if (ast_app_has_voicemail(ext_context, NULL)) 
3423                         ast_smdi_mwi_set(smdi_iface, extension);
3424                 else
3425                         ast_smdi_mwi_unset(smdi_iface, extension);
3426
3427                 if ((mwi_msg = ast_smdi_mwi_message_wait_station(smdi_iface, SMDI_MWI_WAIT_TIMEOUT, extension))) {
3428                         ast_log(AST_LOG_ERROR, "Error executing SMDI MWI change for %s\n", extension);
3429                         if (!strncmp(mwi_msg->cause, "INV", 3))
3430                                 ast_log(AST_LOG_ERROR, "Invalid MWI extension: %s\n", mwi_msg->fwd_st);
3431                         else if (!strncmp(mwi_msg->cause, "BLK", 3))
3432                                 ast_log(AST_LOG_WARNING, "MWI light was already on or off for %s\n", mwi_msg->fwd_st);
3433                         ast_log(AST_LOG_WARNING, "The switch reported '%s'\n", mwi_msg->cause);
3434                         ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
3435                 } else {
3436                         ast_debug(1, "Successfully executed SMDI MWI change for %s\n", extension);
3437                 }
3438         }
3439
3440         if (!ast_strlen_zero(externnotify)) {
3441                 if (inboxcount(ext_context, &newvoicemails, &oldvoicemails)) {
3442                         ast_log(AST_LOG_ERROR, "Problem in calculating number of voicemail messages available for extension %s\n", extension);
3443                 } else {
3444                         snprintf(arguments, sizeof(arguments), "%s %s %s %d&", externnotify, context, extension, newvoicemails);
3445                         ast_debug(1, "Executing %s\n", arguments);
3446                         ast_safe_system(arguments);
3447                 }
3448         }
3449 }
3450
3451 /*!
3452  * \brief Variables used for saving a voicemail.
3453  *
3454  * This includes the record gain, mode flags, and the exit context of the chanel that was used for leaving the voicemail.
3455  */
3456 struct leave_vm_options {
3457         unsigned int flags;
3458         signed char record_gain;
3459         char *exitcontext;
3460 };
3461
3462 /*!
3463  * \brief Prompts the user and records a voicemail to a mailbox.
3464  * \param chan
3465  * \param ext
3466  * \param options OPT_BUSY_GREETING, OPT_UNAVAIL_GREETING
3467  * 
3468  * 
3469  * 
3470  * \return zero on success, -1 on error.
3471  */
3472 static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_options *options)
3473 {
3474 #ifdef IMAP_STORAGE
3475         int newmsgs;
3476         int oldmsgs;
3477         struct vm_state *vms = NULL;
3478 #endif
3479         char txtfile[PATH_MAX];
3480         char tmptxtfile[PATH_MAX];
3481         char callerid[256];
3482         FILE *txt;
3483         char date[256];
3484         int txtdes;
3485         int res = 0;
3486         int msgnum;
3487         int duration = 0;
3488         int ausemacro = 0;
3489         int ousemacro = 0;
3490         int ouseexten = 0;
3491         int rtmsgid = 0;
3492         char tmpid[16];
3493         char tmpdur[16];
3494         char priority[16];
3495         char origtime[16];
3496         char dir[PATH_MAX];
3497         char tmpdir[PATH_MAX];
3498         char fn[PATH_MAX];
3499         char prefile[PATH_MAX] = "";
3500         char tempfile[PATH_MAX] = "";
3501         char ext_context[256] = "";
3502         char fmt[80];
3503         char *context;
3504         char ecodes[17] = "#";
3505         char tmp[1024] = "";
3506         char *tmpptr;
3507         struct ast_vm_user *vmu;
3508         struct ast_vm_user svm;
3509         const char *category = NULL;
3510         const char *code;
3511         const char *alldtmf = "0123456789ABCD*#";
3512
3513         ast_copy_string(tmp, ext, sizeof(tmp));
3514         ext = tmp;
3515         if ((context = strchr(tmp, '@'))) {
3516                 *context++ = '\0';
3517                 tmpptr = strchr(context, '&');
3518         } else {
3519                 tmpptr = strchr(ext, '&');
3520         }
3521
3522         if (tmpptr)
3523                 *tmpptr++ = '\0';
3524
3525         category = pbx_builtin_getvar_helper(chan, "VM_CATEGORY");
3526
3527         ast_debug(3, "Before find_user\n");
3528         if (!(vmu = find_user(&svm, context, ext))) {
3529                 ast_log(AST_LOG_WARNING, "No entry in voicemail config file for '%s'\n", ext);
3530                 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
3531                 return res;
3532         }
3533         /* Setup pre-file if appropriate */
3534         if (strcmp(vmu->context, "default"))
3535                 snprintf(ext_context, sizeof(ext_context), "%s@%s", ext, vmu->context);
3536         else
3537                 ast_copy_string(ext_context, vmu->mailbox, sizeof(ext_context));
3538
3539         /* Set the path to the prefile. Will be one of 
3540                 VM_SPOOL_DIRcontext/ext/busy
3541                 VM_SPOOL_DIRcontext/ext/unavail
3542            Depending on the flag set in options.
3543         */
3544         if (ast_test_flag(options, OPT_BUSY_GREETING)) {
3545                 snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, ext);
3546         } else if (ast_test_flag(options, OPT_UNAVAIL_GREETING)) {
3547                 snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, ext);
3548         }
3549         /* Set the path to the tmpfile as
3550                 VM_SPOOL_DIR/context/ext/temp
3551            and attempt to create the folder structure.
3552         */
3553         snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", VM_SPOOL_DIR, vmu->context, ext);
3554         if ((res = create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, ext, "tmp"))) {
3555                 ast_log(AST_LOG_WARNING, "Failed to make directory (%s)\n", tempfile);
3556                 return -1;
3557         }
3558
3559         RETRIEVE(tempfile, -1, ext, context);
3560
3561         if (ast_fileexists(tempfile, NULL, NULL) > 0)
3562                 ast_copy_string(prefile, tempfile, sizeof(prefile));
3563
3564         DISPOSE(tempfile, -1);
3565         /* It's easier just to try to make it than to check for its existence */
3566         create_dirpath(dir, sizeof(dir), vmu->context, ext, "INBOX");
3567
3568         /* Check current or macro-calling context for special extensions */
3569         if (ast_test_flag(vmu, VM_OPERATOR)) {
3570                 if (!ast_strlen_zero(vmu->exit)) {
3571                         if (ast_exists_extension(chan, vmu->exit, "o", 1, chan->cid.cid_num)) {
3572                                 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
3573                                 ouseexten = 1;
3574                         }
3575                 } else if (ast_exists_extension(chan, chan->context, "o", 1, chan->cid.cid_num)) {
3576                         strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
3577                         ouseexten = 1;
3578                 } else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num)) {
3579                         strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
3580                         ousemacro = 1;
3581                 }
3582         }
3583
3584         if (!ast_strlen_zero(vmu->exit)) {
3585                 if (ast_exists_extension(chan, vmu->exit, "a", 1, chan->cid.cid_num))
3586                         strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
3587         } else if (ast_exists_extension(chan, chan->context, "a", 1, chan->cid.cid_num))
3588                 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
3589         else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "a", 1, chan->cid.cid_num)) {
3590                 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);