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