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