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