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