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