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