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