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