- convert the list of zones to use the list macros, and add locking (issue #7027...
[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  * \par See also
26  * \arg \ref Config_vm
27  * \ingroup applications
28  */
29
30 /*
31  * 12-16-2004 : Support for Greek added by InAccess Networks (work funded by HOL, www.hol.gr)
32  *                               George Konstantoulakis <gkon@inaccessnetworks.com>
33  *
34  * 05-10-2005 : Support for Swedish and Norwegian added by Daniel Nylander, http://www.danielnylander.se/
35  *
36  * 05-11-2005 : An option for maximum number of messsages per mailbox added by GDS Partners (www.gdspartners.com)
37  * 07-11-2005 : An issue with voicemail synchronization has been fixed by GDS Partners (www.gdspartners.com)
38  *                               Stojan Sljivic <stojan.sljivic@gdspartners.com>
39  *
40  */
41
42 #include <stdlib.h>
43 #include <errno.h>
44 #include <unistd.h>
45 #include <string.h>
46 #include <stdlib.h>
47 #include <stdio.h>
48 #include <sys/time.h>
49 #include <sys/stat.h>
50 #include <sys/types.h>
51 #include <sys/mman.h>
52 #include <time.h>
53 #include <dirent.h>
54
55 #include "asterisk.h"
56
57 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
58
59 #include "asterisk/lock.h"
60 #include "asterisk/file.h"
61 #include "asterisk/logger.h"
62 #include "asterisk/channel.h"
63 #include "asterisk/pbx.h"
64 #include "asterisk/options.h"
65 #include "asterisk/config.h"
66 #include "asterisk/say.h"
67 #include "asterisk/module.h"
68 #include "asterisk/adsi.h"
69 #include "asterisk/app.h"
70 #include "asterisk/manager.h"
71 #include "asterisk/dsp.h"
72 #include "asterisk/localtime.h"
73 #include "asterisk/cli.h"
74 #include "asterisk/utils.h"
75 #include "asterisk/stringfields.h"
76 #ifdef WITH_SMDI
77 #include "asterisk/smdi.h"
78 #define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
79 #endif
80 #ifdef USE_ODBC_STORAGE
81 #include "asterisk/res_odbc.h"
82 #endif
83
84 #define COMMAND_TIMEOUT 5000
85 #define VOICEMAIL_DIR_MODE      0770
86 #define VOICEMAIL_FILE_MODE     0660
87
88 #define VOICEMAIL_CONFIG "voicemail.conf"
89 #define ASTERISK_USERNAME "asterisk"
90
91 /* Default mail command to mail voicemail. Change it with the
92     mailcmd= command in voicemail.conf */
93 #define SENDMAIL "/usr/sbin/sendmail -t"
94
95 #define INTRO "vm-intro"
96
97 #define MAXMSG 100
98 #define MAXMSGLIMIT 9999
99
100 #define BASEMAXINLINE 256
101 #define BASELINELEN 72
102 #define BASEMAXINLINE 256
103 #define eol "\r\n"
104
105 #define MAX_DATETIME_FORMAT     512
106 #define MAX_NUM_CID_CONTEXTS 10
107
108 #define VM_REVIEW               (1 << 0)
109 #define VM_OPERATOR             (1 << 1)
110 #define VM_SAYCID               (1 << 2)
111 #define VM_SVMAIL               (1 << 3)
112 #define VM_ENVELOPE             (1 << 4)
113 #define VM_SAYDURATION          (1 << 5)
114 #define VM_SKIPAFTERCMD         (1 << 6)
115 #define VM_FORCENAME            (1 << 7)        /*!< Have new users record their name */
116 #define VM_FORCEGREET           (1 << 8)        /*!< Have new users record their greetings */
117 #define VM_PBXSKIP              (1 << 9)
118 #define VM_DIRECFORWARD         (1 << 10)       /*!< directory_forward */
119 #define VM_ATTACH               (1 << 11)
120 #define VM_DELETE               (1 << 12)
121 #define VM_ALLOCED              (1 << 13)
122 #define VM_SEARCH               (1 << 14)
123
124 #define ERROR_LOCK_PATH         -100
125
126 enum {
127         OPT_SILENT =           (1 << 0),
128         OPT_BUSY_GREETING =    (1 << 1),
129         OPT_UNAVAIL_GREETING = (1 << 2),
130         OPT_RECORDGAIN =       (1 << 3),
131         OPT_PREPEND_MAILBOX =  (1 << 4),
132         OPT_PRIORITY_JUMP =    (1 << 5),
133         OPT_AUTOPLAY =         (1 << 6),
134 } vm_option_flags;
135
136 enum {
137         OPT_ARG_RECORDGAIN = 0,
138         OPT_ARG_PLAYFOLDER = 1,
139         /* This *must* be the last value in this enum! */
140         OPT_ARG_ARRAY_SIZE = 2,
141 } vm_option_args;
142
143 AST_APP_OPTIONS(vm_app_options, {
144         AST_APP_OPTION('s', OPT_SILENT),
145         AST_APP_OPTION('b', OPT_BUSY_GREETING),
146         AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
147         AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
148         AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
149         AST_APP_OPTION('j', OPT_PRIORITY_JUMP),
150         AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
151 });
152
153 static int load_config(void);
154
155 /*! \page vmlang Voicemail Language Syntaxes Supported
156
157         \par Syntaxes supported, not really language codes.
158         \arg \b en - English
159         \arg \b de - German
160         \arg \b es - Spanish
161         \arg \b fr - French
162         \arg \b it = Italian
163         \arg \b nl - Dutch
164         \arg \b pt - Portuguese
165         \arg \b gr - Greek
166         \arg \b no - Norwegian
167         \arg \b se - Swedish
168
169 German requires the following additional soundfile:
170 \arg \b 1F      einE (feminine)
171
172 Spanish requires the following additional soundfile:
173 \arg \b 1M      un (masculine)
174
175 Dutch, Portuguese & Spanish require the following additional soundfiles:
176 \arg \b vm-INBOXs       singular of 'new'
177 \arg \b vm-Olds         singular of 'old/heard/read'
178
179 NB these are plural:
180 \arg \b vm-INBOX        nieuwe (nl)
181 \arg \b vm-Old          oude (nl)
182
183 Swedish uses:
184 \arg \b vm-nytt         singular of 'new'
185 \arg \b vm-nya          plural of 'new'
186 \arg \b vm-gammalt      singular of 'old'
187 \arg \b vm-gamla        plural of 'old'
188 \arg \b digits/ett      'one', not always same as 'digits/1'
189
190 Norwegian uses:
191 \arg \b vm-ny           singular of 'new'
192 \arg \b vm-nye          plural of 'new'
193 \arg \b vm-gammel       singular of 'old'
194 \arg \b vm-gamle        plural of 'old'
195
196 Dutch also uses:
197 \arg \b nl-om           'at'?
198
199 Spanish also uses:
200 \arg \b vm-youhaveno
201
202 Italian requires the following additional soundfile:
203
204 For vm_intro_it:
205 \arg \b vm-nuovo        new
206 \arg \b vm-nuovi        new plural
207 \arg \b vm-vecchio      old
208 \arg \b vm-vecchi       old plural
209
210 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
211 spelled among others when you have to change folder. For the above reasons, vm-INBOX
212 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
213
214 */
215
216 struct baseio {
217         int iocp;
218         int iolen;
219         int linelength;
220         int ateof;
221         unsigned char iobuf[BASEMAXINLINE];
222 };
223
224 /*! Structure for linked list of users */
225 struct ast_vm_user {
226         char context[AST_MAX_CONTEXT];  /*!< Voicemail context */
227         char mailbox[AST_MAX_EXTENSION];/*!< Mailbox id, unique within vm context */
228         char password[80];              /*!< Secret pin code, numbers only */
229         char fullname[80];              /*!< Full name, for directory app */
230         char email[80];                 /*!< E-mail address */
231         char pager[80];                 /*!< E-mail address to pager (no attachment) */
232         char serveremail[80];           /*!< From: Mail address */
233         char mailcmd[160];              /*!< Configurable mail command */
234         char language[MAX_LANGUAGE];    /*!< Config: Language setting */
235         char zonetag[80];               /*!< Time zone */
236         char callback[80];
237         char dialout[80];
238         char uniqueid[20];              /*!< Unique integer identifier */
239         char exit[80];
240         char attachfmt[20];             /*!< Attachment format */
241         unsigned int flags;             /*!< VM_ flags */       
242         int saydurationm;
243         int maxmsg;                     /*!< Maximum number of msgs per folder for this mailbox */
244         AST_LIST_ENTRY(ast_vm_user) list;
245 };
246
247 struct vm_zone {
248         AST_LIST_ENTRY(vm_zone) list;
249         char name[80];
250         char timezone[80];
251         char msg_format[512];
252 };
253
254 struct vm_state {
255         char curbox[80];
256         char username[80];
257         char curdir[256];
258         char vmbox[256];
259         char fn[256];
260         char fn2[256];
261         int *deleted;
262         int *heard;
263         int curmsg;
264         int lastmsg;
265         int newmessages;
266         int oldmessages;
267         int starting;
268         int repeats;
269 };
270 static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msg,
271                             int option, signed char record_gain);
272 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
273 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
274                               char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
275                               signed char record_gain);
276 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
277 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
278
279 static void apply_options(struct ast_vm_user *vmu, const char *options);
280
281 #ifdef USE_ODBC_STORAGE
282 static char odbc_database[80];
283 static char odbc_table[80];
284 #define RETRIEVE(a,b) retrieve_file(a,b)
285 #define DISPOSE(a,b) remove_file(a,b)
286 #define STORE(a,b,c,d) store_file(a,b,c,d)
287 #define EXISTS(a,b,c,d) (message_exists(a,b))
288 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
289 #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
290 #define DELETE(a,b,c) (delete_file(a,b))
291 #else
292 #define RETRIEVE(a,b)
293 #define DISPOSE(a,b)
294 #define STORE(a,b,c,d)
295 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
296 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
297 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
298 #define DELETE(a,b,c) (vm_delete(c))
299 #endif
300
301 static char VM_SPOOL_DIR[AST_CONFIG_MAX_PATH];
302
303 static char ext_pass_cmd[128];
304
305 static char *tdesc = "Comedian Mail (Voicemail System)";
306
307 static char *addesc = "Comedian Mail";
308
309 static char *synopsis_vm =
310 "Leave a Voicemail message";
311
312 static char *descrip_vm =
313 "  VoiceMail(mailbox[@context][&mailbox[@context]][...][|options]): This\n"
314 "application allows the calling party to leave a message for the specified\n"
315 "list of mailboxes. When multiple mailboxes are specified, the greeting will\n"
316 "be taken from the first mailbox specified. Dialplan execution will stop if the\n"
317 "specified mailbox does not exist.\n"
318 "  The Voicemail application will exit if any of the following DTMF digits are\n"
319 "received:\n"
320 "    0 - Jump to the 'o' extension in the current dialplan context.\n"
321 "    * - Jump to the 'a' extension in the current dialplan context.\n"
322 "  This application will set the following channel variable upon completion:\n"
323 "    VMSTATUS - This indicates the status of the execution of the VoiceMail\n"
324 "               application. The possible values are:\n"
325 "               SUCCESS | USEREXIT | FAILED\n\n"
326 "  Options:\n"
327 "    b    - Play the 'busy' greeting to the calling party.\n"
328 "    g(#) - Use the specified amount of gain when recording the voicemail\n"
329 "           message. The units are whole-number decibels (dB).\n"
330 "    s    - Skip the playback of instructions for leaving a message to the\n"
331 "           calling party.\n"
332 "    u    - Play the 'unavailable greeting.\n"
333 "    j    - Jump to priority n+101 if the mailbox is not found or some other\n"
334 "           error occurs.\n";
335
336 static char *synopsis_vmain =
337 "Check Voicemail messages";
338
339 static char *descrip_vmain =
340 "  VoiceMailMain([mailbox][@context][|options]): This application allows the\n"
341 "calling party to check voicemail messages. A specific mailbox, and optional\n"
342 "corresponding context, may be specified. If a mailbox is not provided, the\n"
343 "calling party will be prompted to enter one. If a context is not specified,\n"
344 "the 'default' context will be used.\n\n"
345 "  Options:\n"
346 "    p    - Consider the mailbox parameter as a prefix to the mailbox that\n"
347 "           is entered by the caller.\n"
348 "    g(#) - Use the specified amount of gain when recording a voicemail\n"
349 "           message. The units are whole-number decibels (dB).\n"
350 "    s    - Skip checking the passcode for the mailbox.\n"
351 "    a(#) - Skip folder prompt and go directly to folder specified.\n"
352 "           Defaults to INBOX\n";
353
354 static char *synopsis_vm_box_exists =
355 "Check to see if Voicemail mailbox exists";
356
357 static char *descrip_vm_box_exists =
358 "  MailboxExists(mailbox[@context][|options]): Check to see if the specified\n"
359 "mailbox exists. If no voicemail context is specified, the 'default' context\n"
360 "will be used.\n"
361 "  This application will set the following channel variable upon completion:\n"
362 "    VMBOXEXISTSSTATUS - This will contain the status of the execution of the\n"
363 "                        MailboxExists application. Possible values include:\n"
364 "                        SUCCESS | FAILED\n\n"
365 "  Options:\n"
366 "    j - Jump to priority n+101 if the mailbox is found.\n";
367
368 static char *synopsis_vmauthenticate =
369 "Authenticate with Voicemail passwords";
370
371 static char *descrip_vmauthenticate =
372 "  VMAuthenticate([mailbox][@context][|options]): This application behaves the\n"
373 "same way as the Authenticate application, but the passwords are taken from\n"
374 "voicemail.conf.\n"
375 "  If the mailbox is specified, only that mailbox's password will be considered\n"
376 "valid. If the mailbox is not specified, the channel variable AUTH_MAILBOX will\n"
377 "be set with the authenticated mailbox.\n\n"
378 "  Options:\n"
379 "    s - Skip playing the initial prompts.\n";
380
381 /* Leave a message */
382 static char *app = "VoiceMail";
383
384 /* Check mail, control, etc */
385 static char *app2 = "VoiceMailMain";
386
387 static char *app3 = "MailboxExists";
388 static char *app4 = "VMAuthenticate";
389
390 static AST_LIST_HEAD_STATIC(users, ast_vm_user);
391 static AST_LIST_HEAD_STATIC(zones, vm_zone);
392 static int maxsilence;
393 static int maxmsg;
394 static int silencethreshold = 128;
395 static char serveremail[80];
396 static char mailcmd[160];       /* Configurable mail cmd */
397 static char externnotify[160]; 
398 #ifdef WITH_SMDI
399 static struct ast_smdi_interface *smdi_iface = NULL;
400 #endif
401 static char vmfmts[80];
402 static int vmminmessage;
403 static int vmmaxmessage;
404 static int maxgreet;
405 static int skipms;
406 static int maxlogins;
407
408 static struct ast_flags globalflags = {0};
409
410 static int saydurationminfo;
411
412 static char dialcontext[AST_MAX_CONTEXT];
413 static char callcontext[AST_MAX_CONTEXT];
414 static char exitcontext[AST_MAX_CONTEXT];
415
416 static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
417
418
419 static char *emailbody = NULL;
420 static char *emailsubject = NULL;
421 static char *pagerbody = NULL;
422 static char *pagersubject = NULL;
423 static char fromstring[100];
424 static char pagerfromstring[100];
425 static char emailtitle[100];
426 static char charset[32] = "ISO-8859-1";
427
428 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
429 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
430 static int adsiver = 1;
431 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
432
433 LOCAL_USER_DECL;
434
435 static void populate_defaults(struct ast_vm_user *vmu)
436 {
437         ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);     
438         if (saydurationminfo)
439                 vmu->saydurationm = saydurationminfo;
440         if (callcontext)
441                 ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
442         if (dialcontext)
443                 ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
444         if (exitcontext)
445                 ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
446         if (maxmsg)
447                 vmu->maxmsg = maxmsg;
448 }
449
450 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
451 {
452         int x;
453         if (!strcasecmp(var, "attach")) {
454                 ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
455         } else if (!strcasecmp(var, "attachfmt")) {
456                 ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
457         } else if (!strcasecmp(var, "serveremail")) {
458                 ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
459         } else if (!strcasecmp(var, "language")) {
460                 ast_copy_string(vmu->language, value, sizeof(vmu->language));
461         } else if (!strcasecmp(var, "tz")) {
462                 ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
463         } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
464                 ast_set2_flag(vmu, ast_true(value), VM_DELETE); 
465         } else if (!strcasecmp(var, "saycid")){
466                 ast_set2_flag(vmu, ast_true(value), VM_SAYCID); 
467         } else if (!strcasecmp(var,"sendvoicemail")){
468                 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL); 
469         } else if (!strcasecmp(var, "review")){
470                 ast_set2_flag(vmu, ast_true(value), VM_REVIEW); 
471         } else if (!strcasecmp(var, "operator")){
472                 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);       
473         } else if (!strcasecmp(var, "envelope")){
474                 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);       
475         } else if (!strcasecmp(var, "sayduration")){
476                 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);    
477         } else if (!strcasecmp(var, "saydurationm")){
478                 if (sscanf(value, "%d", &x) == 1) {
479                         vmu->saydurationm = x;
480                 } else {
481                         ast_log(LOG_WARNING, "Invalid min duration for say duration\n");
482                 }
483         } else if (!strcasecmp(var, "forcename")){
484                 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);      
485         } else if (!strcasecmp(var, "forcegreetings")){
486                 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);     
487         } else if (!strcasecmp(var, "callback")) {
488                 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
489         } else if (!strcasecmp(var, "dialout")) {
490                 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
491         } else if (!strcasecmp(var, "exitcontext")) {
492                 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
493         } else if (!strcasecmp(var, "maxmsg")) {
494                 vmu->maxmsg = atoi(value);
495                 if (vmu->maxmsg <= 0) {
496                         ast_log(LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %i\n", value, MAXMSG);
497                         vmu->maxmsg = MAXMSG;
498                 } else if (vmu->maxmsg > MAXMSGLIMIT) {
499                         ast_log(LOG_WARNING, "Maximum number of messages per folder is %i. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
500                         vmu->maxmsg = MAXMSGLIMIT;
501                 }
502         } else if (!strcasecmp(var, "options")) {
503                 apply_options(vmu, value);
504         }
505 }
506
507 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
508 {
509         int res;
510         if (!ast_strlen_zero(vmu->uniqueid)) {
511                 res = ast_update_realtime("voicemail", "uniqueid", vmu->uniqueid, "password", password, NULL);
512                 if (res > 0) {
513                         ast_copy_string(vmu->password, password, sizeof(vmu->password));
514                         res = 0;
515                 } else if (!res) {
516                         res = -1;
517                 }
518                 return res;
519         }
520         return -1;
521 }
522
523 static void apply_options(struct ast_vm_user *vmu, const char *options)
524 {       /* Destructively Parse options and apply */
525         char *stringp;
526         char *s;
527         char *var, *value;
528         stringp = ast_strdupa(options);
529         while ((s = strsep(&stringp, "|"))) {
530                 value = s;
531                 if ((var = strsep(&value, "=")) && value) {
532                         apply_option(vmu, var, value);
533                 }
534         }       
535 }
536
537 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
538 {
539         struct ast_variable *var, *tmp;
540         struct ast_vm_user *retval;
541
542         if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
543                 if (!ivm)
544                         ast_set_flag(retval, VM_ALLOCED);       
545                 else
546                         memset(retval, 0, sizeof(*retval));
547                 if (mailbox) 
548                         ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
549                 populate_defaults(retval);
550                 if (!context && ast_test_flag((&globalflags), VM_SEARCH))
551                         var = ast_load_realtime("voicemail", "mailbox", mailbox, NULL);
552                 else
553                         var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, NULL);
554                 if (var) {
555                         tmp = var;
556                         while(tmp) {
557                                 printf("%s => %s\n", tmp->name, tmp->value);
558                                 if (!strcasecmp(tmp->name, "password")) {
559                                         ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
560                                 } else if (!strcasecmp(tmp->name, "uniqueid")) {
561                                         ast_copy_string(retval->uniqueid, tmp->value, sizeof(retval->uniqueid));
562                                 } else if (!strcasecmp(tmp->name, "pager")) {
563                                         ast_copy_string(retval->pager, tmp->value, sizeof(retval->pager));
564                                 } else if (!strcasecmp(tmp->name, "email")) {
565                                         ast_copy_string(retval->email, tmp->value, sizeof(retval->email));
566                                 } else if (!strcasecmp(tmp->name, "fullname")) {
567                                         ast_copy_string(retval->fullname, tmp->value, sizeof(retval->fullname));
568                                 } else if (!strcasecmp(tmp->name, "context")) {
569                                         ast_copy_string(retval->context, tmp->value, sizeof(retval->context));
570                                 } else
571                                         apply_option(retval, tmp->name, tmp->value);
572                                 tmp = tmp->next;
573                         } 
574                 } else { 
575                         if (!ivm) 
576                                 free(retval);
577                         retval = NULL;
578                 }       
579         } 
580         return retval;
581 }
582
583 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
584 {
585         /* This function could be made to generate one from a database, too */
586         struct ast_vm_user *vmu=NULL, *cur;
587         AST_LIST_LOCK(&users);
588
589         if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
590                 context = "default";
591
592         AST_LIST_TRAVERSE(&users, cur, list) {
593                 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
594                         break;
595                 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
596                         break;
597         }
598         if (cur) {
599                 /* Make a copy, so that on a reload, we have no race */
600                 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
601                         memcpy(vmu, cur, sizeof(*vmu));
602                         ast_set2_flag(vmu, !ivm, VM_ALLOCED);
603                         AST_LIST_NEXT(vmu, list) = NULL;
604                 }
605         } else
606                 vmu = find_user_realtime(ivm, context, mailbox);
607         AST_LIST_UNLOCK(&users);
608         return vmu;
609 }
610
611 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
612 {
613         /* This function could be made to generate one from a database, too */
614         struct ast_vm_user *cur;
615         int res = -1;
616         AST_LIST_LOCK(&users);
617         AST_LIST_TRAVERSE(&users, cur, list) {
618                 if ((!context || !strcasecmp(context, cur->context)) &&
619                         (!strcasecmp(mailbox, cur->mailbox)))
620                                 break;
621         }
622         if (cur) {
623                 ast_copy_string(cur->password, newpass, sizeof(cur->password));
624                 res = 0;
625         }
626         AST_LIST_UNLOCK(&users);
627         return res;
628 }
629
630 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
631 {
632         /*  There's probably a better way of doing this. */
633         /*  That's why I've put the password change in a separate function. */
634         /*  This could also be done with a database function */
635         
636         FILE *configin;
637         FILE *configout;
638         int linenum=0;
639         char inbuf[256];
640         char orig[256];
641         char currcontext[256] ="";
642         char tmpin[AST_CONFIG_MAX_PATH];
643         char tmpout[AST_CONFIG_MAX_PATH];
644         struct stat statbuf;
645
646         if (!change_password_realtime(vmu, newpassword))
647                 return;
648
649         snprintf(tmpin, sizeof(tmpin), "%s/voicemail.conf", ast_config_AST_CONFIG_DIR);
650         snprintf(tmpout, sizeof(tmpout), "%s/voicemail.conf.new", ast_config_AST_CONFIG_DIR);
651         configin = fopen(tmpin,"r");
652         if (configin)
653                 configout = fopen(tmpout,"w+");
654         else
655                 configout = NULL;
656         if (!configin || !configout) {
657                 if (configin)
658                         fclose(configin);
659                 else
660                         ast_log(LOG_WARNING, "Warning: Unable to open '%s' for reading: %s\n", tmpin, strerror(errno));
661                 if (configout)
662                         fclose(configout);
663                 else
664                         ast_log(LOG_WARNING, "Warning: Unable to open '%s' for writing: %s\n", tmpout, strerror(errno));
665                         return;
666         }
667
668         while (!feof(configin)) {
669                 char *user = NULL, *pass = NULL, *rest = NULL, *comment = NULL, *tmpctx = NULL, *tmpctxend = NULL;
670
671                 /* Read in the line */
672                 fgets(inbuf, sizeof(inbuf), configin);
673                 linenum++;
674
675                 if (ast_strlen_zero(inbuf)) {
676                         fprintf(configout, "\n");
677                         continue;
678                 }
679
680                 /* Make a backup of it */
681                 ast_copy_string(orig, inbuf, sizeof(orig));
682
683                 /*
684                   Read the file line by line, split each line into a comment and command section
685                   only parse the command portion of the line
686                 */
687                 if (inbuf[strlen(inbuf) - 1] == '\n')
688                         inbuf[strlen(inbuf) - 1] = '\0';
689
690                 if ((comment = strchr(inbuf, ';')))
691                         *comment++ = '\0'; /* Now inbuf is terminated just before the comment */
692
693                 if (ast_strlen_zero(inbuf)) {
694                         fprintf(configout, "%s", orig);
695                         continue;
696                 }
697
698                 /* Check for a context, first '[' to first ']' */
699                 if ((tmpctx = strchr(inbuf, '['))) {
700                         tmpctxend = strchr(tmpctx, ']');
701                         if (tmpctxend) {
702                                 /* Valid context */
703                                 ast_copy_string(currcontext, tmpctx + 1, tmpctxend - tmpctx);
704                                 fprintf(configout, "%s", orig);
705                                 continue;
706                         }
707                 }
708
709                 /* This isn't a context line, check for MBX => PSWD... */
710                 user = inbuf;
711                 if ((pass = strchr(user, '='))) {
712                         /* We have a line in the form of aaaaa=aaaaaa */
713                         *pass++ = '\0';
714
715                         user = ast_strip(user);
716
717                         if (*pass == '>')
718                                 *pass++ = '\0';
719
720                         pass = ast_skip_blanks(pass);
721
722                         /* 
723                            Since no whitespace allowed in fields, or more correctly white space
724                            inside the fields is there for a purpose, we can just terminate pass
725                            at the comma or EOL whichever comes first.
726                         */
727                         if ((rest = strchr(pass, ',')))
728                                 *rest++ = '\0';
729                 } else {
730                         user = NULL;
731                 }                       
732
733                 /* Compare user, pass AND context */
734                 if (!ast_strlen_zero(user) && !strcmp(user, vmu->mailbox) &&
735                     !ast_strlen_zero(pass) && !strcmp(pass, vmu->password) &&
736                     !strcasecmp(currcontext, vmu->context)) {
737                         /* This is the line */
738                         if (rest) {
739                                 fprintf(configout, "%s => %s,%s", user, newpassword, rest);
740                         } else {
741                                 fprintf(configout, "%s => %s", user, newpassword);
742                         }
743                         /* If there was a comment on the line print it out */
744                         if (comment) {
745                                 fprintf(configout, ";%s\n", comment);
746                         } else {
747                                 fprintf(configout, "\n");
748                         }
749                 } else {
750                         /* Put it back like it was */
751                         fprintf(configout, "%s", orig);
752                 }
753         }
754         fclose(configin);
755         fclose(configout);
756
757         stat(tmpin, &statbuf);
758         chmod(tmpout, statbuf.st_mode);
759         chown(tmpout, statbuf.st_uid, statbuf.st_gid);
760         unlink(tmpin);
761         rename(tmpout, tmpin);
762         reset_user_pw(vmu->context, vmu->mailbox, newpassword);
763         ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
764 }
765
766 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
767 {
768         char buf[255];
769         snprintf(buf,255,"%s %s %s %s",ext_pass_cmd,vmu->context,vmu->mailbox,newpassword);
770         if (!ast_safe_system(buf))
771                 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
772 }
773
774 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
775 {
776         return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
777 }
778
779 static int make_file(char *dest, int len, char *dir, int num)
780 {
781         return snprintf(dest, len, "%s/msg%04d", dir, num);
782 }
783
784 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
785  * \param dest    String. base directory.
786  * \param context String. Ignored if is null or empty string.
787  * \param ext     String. Ignored if is null or empty string.
788  * \param folder  String. Ignored if is null or empty string. 
789  * \return 0 on failure, 1 on success.
790  */
791 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
792 {
793         mode_t  mode = VOICEMAIL_DIR_MODE;
794
795         if (!ast_strlen_zero(context)) {
796                 make_dir(dest, len, context, "", "");
797                 if(mkdir(dest, mode) && errno != EEXIST) {
798                         ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dest, strerror(errno));
799                         return 0;
800                 }
801         }
802         if (!ast_strlen_zero(ext)) {
803                 make_dir(dest, len, context, ext, "");
804                 if(mkdir(dest, mode) && errno != EEXIST) {
805                         ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dest, strerror(errno));
806                         return 0;
807                 }
808         }
809         if (!ast_strlen_zero(folder)) {
810                 make_dir(dest, len, context, ext, folder);
811                 if(mkdir(dest, mode) && errno != EEXIST) {
812                         ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dest, strerror(errno));
813                         return 0;
814                 }
815         }
816         return 1;
817 }
818
819 /* only return failure if ast_lock_path returns 'timeout',
820    not if the path does not exist or any other reason
821 */
822 static int vm_lock_path(const char *path)
823 {
824         switch (ast_lock_path(path)) {
825         case AST_LOCK_TIMEOUT:
826                 return -1;
827         default:
828                 return 0;
829         }
830 }
831
832
833 #ifdef USE_ODBC_STORAGE
834 static int retrieve_file(char *dir, int msgnum)
835 {
836         int x = 0;
837         int res;
838         int fd=-1;
839         size_t fdlen = 0;
840         void *fdm=NULL;
841         SQLSMALLINT colcount=0;
842         SQLHSTMT stmt;
843         char sql[256];
844         char fmt[80]="";
845         char *c;
846         char coltitle[256];
847         SQLSMALLINT collen;
848         SQLSMALLINT datatype;
849         SQLSMALLINT decimaldigits;
850         SQLSMALLINT nullable;
851         SQLULEN colsize;
852         FILE *f=NULL;
853         char rowdata[80];
854         char fn[256];
855         char full_fn[256];
856         char msgnums[80];
857         
858         odbc_obj *obj;
859         obj = fetch_odbc_obj(odbc_database, 0);
860         if (obj) {
861                 ast_copy_string(fmt, vmfmts, sizeof(fmt));
862                 c = strchr(fmt, '|');
863                 if (c)
864                         *c = '\0';
865                 if (!strcasecmp(fmt, "wav49"))
866                         strcpy(fmt, "WAV");
867                 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
868                 if (msgnum > -1)
869                         make_file(fn, sizeof(fn), dir, msgnum);
870                 else
871                         ast_copy_string(fn, dir, sizeof(fn));
872                 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
873                 f = fopen(full_fn, "w+");
874                 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
875                 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
876                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
877                         ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
878                         goto yuck;
879                 }
880                 snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?",odbc_table);
881                 res = SQLPrepare(stmt, sql, SQL_NTS);
882                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
883                         ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
884                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
885                         goto yuck;
886                 }
887                 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
888                 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
889                 res = odbc_smart_execute(obj, stmt);
890                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
891                         ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
892                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
893                         goto yuck;
894                 }
895                 res = SQLFetch(stmt);
896                 if (res == SQL_NO_DATA) {
897                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
898                         goto yuck;
899                 }
900                 else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
901                         ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
902                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
903                         goto yuck;
904                 }
905                 fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC);
906                 if (fd < 0) {
907                         ast_log(LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
908                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
909                         goto yuck;
910                 }
911                 res = SQLNumResultCols(stmt, &colcount);
912                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {   
913                         ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
914                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
915                         goto yuck;
916                 }
917                 if (f) 
918                         fprintf(f, "[message]\n");
919                 for (x=0;x<colcount;x++) {
920                         rowdata[0] = '\0';
921                         collen = sizeof(coltitle);
922                         res = SQLDescribeCol(stmt, x + 1, coltitle, sizeof(coltitle), &collen, 
923                                                 &datatype, &colsize, &decimaldigits, &nullable);
924                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
925                                 ast_log(LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
926                                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
927                                 goto yuck;
928                         }
929                         if (!strcasecmp(coltitle, "recording")) {
930                                 res = SQLGetData(stmt, x + 1, SQL_BINARY, NULL, 0, &colsize);
931                                 fdlen = colsize;
932                                 fd = open(full_fn, O_RDWR | O_TRUNC | O_CREAT, 0770);
933                                 if (fd > -1) {
934                                         char tmp[1]="";
935                                         lseek(fd, fdlen - 1, SEEK_SET);
936                                         if (write(fd, tmp, 1) != 1) {
937                                                 close(fd);
938                                                 fd = -1;
939                                         }
940                                         if (fd > -1)
941                                                 fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
942                                 }
943                                 if (fdm) {
944                                         memset(fdm, 0, fdlen);
945                                         res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, fdlen, &colsize);
946                                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
947                                                 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
948                                                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
949                                                 goto yuck;
950                                         }
951                                 }
952                         } else {
953                                 res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
954                                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
955                                         ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
956                                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
957                                         goto yuck;
958                                 }
959                                 if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
960                                         fprintf(f, "%s=%s\n", coltitle, rowdata);
961                         }
962                 }
963                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
964         } else
965                 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
966 yuck:   
967         if (f)
968                 fclose(f);
969         if (fdm)
970                 munmap(fdm, fdlen);
971         if (fd > -1)
972                 close(fd);
973         return x - 1;
974 }
975
976 static int remove_file(char *dir, int msgnum)
977 {
978         char fn[256];
979         char full_fn[256];
980         char msgnums[80];
981         
982         if (msgnum > -1) {
983                 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
984                 make_file(fn, sizeof(fn), dir, msgnum);
985         } else
986                 ast_copy_string(fn, dir, sizeof(fn));
987         ast_filedelete(fn, NULL);       
988         snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
989         unlink(full_fn);
990         return 0;
991 }
992
993 static int last_message_index(struct ast_vm_user *vmu, char *dir)
994 {
995         int x = 0;
996         int res;
997         SQLHSTMT stmt;
998         char sql[256];
999         char rowdata[20];
1000         
1001         odbc_obj *obj;
1002         obj = fetch_odbc_obj(odbc_database, 0);
1003         if (obj) {
1004                 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1005                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1006                         ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1007                         goto yuck;
1008                 }
1009                 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?",odbc_table);
1010                 res = SQLPrepare(stmt, sql, SQL_NTS);
1011                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1012                         ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1013                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1014                         goto yuck;
1015                 }
1016                 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
1017                 res = odbc_smart_execute(obj, stmt);
1018                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1019                         ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1020                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1021                         goto yuck;
1022                 }
1023                 res = SQLFetch(stmt);
1024                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1025                         ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
1026                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1027                         goto yuck;
1028                 }
1029                 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
1030                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1031                         ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1032                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1033                         goto yuck;
1034                 }
1035                 if (sscanf(rowdata, "%d", &x) != 1)
1036                         ast_log(LOG_WARNING, "Failed to read message count!\n");
1037                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1038         } else
1039                 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1040 yuck:   
1041         return x - 1;
1042 }
1043
1044 static int message_exists(char *dir, int msgnum)
1045 {
1046         int x = 0;
1047         int res;
1048         SQLHSTMT stmt;
1049         char sql[256];
1050         char rowdata[20];
1051         char msgnums[20];
1052         
1053         odbc_obj *obj;
1054         obj = fetch_odbc_obj(odbc_database, 0);
1055         if (obj) {
1056                 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
1057                 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1058                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1059                         ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1060                         goto yuck;
1061                 }
1062                 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?",odbc_table);
1063                 res = SQLPrepare(stmt, sql, SQL_NTS);
1064                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1065                         ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1066                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1067                         goto yuck;
1068                 }
1069                 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
1070                 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1071                 res = odbc_smart_execute(obj, stmt);
1072                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1073                         ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1074                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1075                         goto yuck;
1076                 }
1077                 res = SQLFetch(stmt);
1078                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1079                         ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
1080                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1081                         goto yuck;
1082                 }
1083                 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
1084                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1085                         ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1086                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1087                         goto yuck;
1088                 }
1089                 if (sscanf(rowdata, "%d", &x) != 1)
1090                         ast_log(LOG_WARNING, "Failed to read message count!\n");
1091                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1092         } else
1093                 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1094 yuck:   
1095         return x;
1096 }
1097
1098 static int count_messages(struct ast_vm_user *vmu, char *dir)
1099 {
1100         return last_message_index(vmu, dir) + 1;
1101 }
1102
1103 static void delete_file(char *sdir, int smsg)
1104 {
1105         int res;
1106         SQLHSTMT stmt;
1107         char sql[256];
1108         char msgnums[20];
1109         
1110         odbc_obj *obj;
1111         obj = fetch_odbc_obj(odbc_database, 0);
1112         if (obj) {
1113                 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1114                 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1115                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1116                         ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1117                         goto yuck;
1118                 }
1119                 snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?",odbc_table);
1120                 res = SQLPrepare(stmt, sql, SQL_NTS);
1121                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1122                         ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1123                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1124                         goto yuck;
1125                 }
1126                 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(sdir), 0, (void *)sdir, 0, NULL);
1127                 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1128                 res = odbc_smart_execute(obj, stmt);
1129                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1130                         ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1131                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1132                         goto yuck;
1133                 }
1134                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1135         } else
1136                 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1137 yuck:
1138         return; 
1139 }
1140
1141 static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailboxuser, char *dmailboxcontext)
1142 {
1143         int res;
1144         SQLHSTMT stmt;
1145         char sql[512];
1146         char msgnums[20];
1147         char msgnumd[20];
1148         odbc_obj *obj;
1149
1150         delete_file(ddir, dmsg);
1151         obj = fetch_odbc_obj(odbc_database, 0);
1152         if (obj) {
1153                 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1154                 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
1155                 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1156                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1157                         ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1158                         goto yuck;
1159                 }
1160 #ifdef EXTENDED_ODBC_STORAGE
1161                 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); 
1162 #else
1163                 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir, msgnum, context, macrocontext, callerid, origtime, duration, recording) SELECT ?,?,context,macrocontext,callerid,origtime,duration,recording FROM %s WHERE dir=? AND msgnum=?",odbc_table,odbc_table); 
1164 #endif
1165                 res = SQLPrepare(stmt, sql, SQL_NTS);
1166                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1167                         ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1168                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1169                         goto yuck;
1170                 }
1171                 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(ddir), 0, (void *)ddir, 0, NULL);
1172                 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnumd), 0, (void *)msgnumd, 0, NULL);
1173 #ifdef EXTENDED_ODBC_STORAGE
1174                 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dmailboxuser), 0, (void *)dmailboxuser, 0, NULL);
1175                 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dmailboxcontext), 0, (void *)dmailboxcontext, 0, NULL);
1176                 SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(sdir), 0, (void *)sdir, 0, NULL);
1177                 SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1178 #else
1179                 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(sdir), 0, (void *)sdir, 0, NULL);
1180                 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1181 #endif           
1182                 res = odbc_smart_execute(obj, stmt);
1183                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1184                         ast_log(LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
1185                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1186                         goto yuck;
1187                 }
1188                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1189         } else
1190                 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1191 yuck:
1192         return; 
1193 }
1194
1195 static int store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum)
1196 {
1197         int x = 0;
1198         int res;
1199         int fd = -1;
1200         void *fdm=NULL;
1201         size_t fdlen = -1;
1202         SQLHSTMT stmt;
1203         SQLINTEGER len;
1204         char sql[256];
1205         char msgnums[20];
1206         char fn[256];
1207         char full_fn[256];
1208         char fmt[80]="";
1209         char *c;
1210         char *context="", *macrocontext="", *callerid="", *origtime="", *duration="";
1211         char *category = "";
1212         struct ast_config *cfg=NULL;
1213         odbc_obj *obj;
1214
1215         delete_file(dir, msgnum);
1216         obj = fetch_odbc_obj(odbc_database, 0);
1217         if (obj) {
1218                 ast_copy_string(fmt, vmfmts, sizeof(fmt));
1219                 c = strchr(fmt, '|');
1220                 if (c)
1221                         *c = '\0';
1222                 if (!strcasecmp(fmt, "wav49"))
1223                         strcpy(fmt, "WAV");
1224                 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
1225                 if (msgnum > -1)
1226                         make_file(fn, sizeof(fn), dir, msgnum);
1227                 else
1228                         ast_copy_string(fn, dir, sizeof(fn));
1229                 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
1230                 cfg = ast_config_load(full_fn);
1231                 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
1232                 fd = open(full_fn, O_RDWR);
1233                 if (fd < 0) {
1234                         ast_log(LOG_WARNING, "Open of sound file '%s' failed: %s\n", full_fn, strerror(errno));
1235                         goto yuck;
1236                 }
1237                 if (cfg) {
1238                         context = ast_variable_retrieve(cfg, "message", "context");
1239                         if (!context) context = "";
1240                         macrocontext = ast_variable_retrieve(cfg, "message", "macrocontext");
1241                         if (!macrocontext) macrocontext = "";
1242                         callerid = ast_variable_retrieve(cfg, "message", "callerid");
1243                         if (!callerid) callerid = "";
1244                         origtime = ast_variable_retrieve(cfg, "message", "origtime");
1245                         if (!origtime) origtime = "";
1246                         duration = ast_variable_retrieve(cfg, "message", "duration");
1247                         if (!duration) duration = "";
1248                         category = ast_variable_retrieve(cfg, "message", "category");
1249                         if (!category) category = "";
1250                 }
1251                 fdlen = lseek(fd, 0, SEEK_END);
1252                 lseek(fd, 0, SEEK_SET);
1253                 printf("Length is %d\n", fdlen);
1254                 fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0);
1255                 if (!fdm) {
1256                         ast_log(LOG_WARNING, "Memory map failed!\n");
1257                         goto yuck;
1258                 } 
1259                 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1260                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1261                         ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1262                         goto yuck;
1263                 }
1264                 if (!ast_strlen_zero(category)) 
1265 #ifdef EXTENDED_ODBC_STORAGE
1266                         snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,category) VALUES (?,?,?,?,?,?,?,?,?,?,?)",odbc_table); 
1267 #else
1268                         snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,category) VALUES (?,?,?,?,?,?,?,?,?)",odbc_table);
1269 #endif
1270                 else
1271 #ifdef EXTENDED_ODBC_STORAGE
1272                         snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext) VALUES (?,?,?,?,?,?,?,?,?,?)",odbc_table);
1273 #else
1274                         snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration) VALUES (?,?,?,?,?,?,?,?)",odbc_table);
1275 #endif
1276                 res = SQLPrepare(stmt, sql, SQL_NTS);
1277                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1278                         ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1279                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1280                         goto yuck;
1281                 }
1282                 len = fdlen; /* SQL_LEN_DATA_AT_EXEC(fdlen); */
1283                 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
1284                 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1285                 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, fdlen, 0, (void *)fdm, fdlen, &len);
1286                 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(context), 0, (void *)context, 0, NULL);
1287                 SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(macrocontext), 0, (void *)macrocontext, 0, NULL);
1288                 SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(callerid), 0, (void *)callerid, 0, NULL);
1289                 SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(origtime), 0, (void *)origtime, 0, NULL);
1290                 SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(duration), 0, (void *)duration, 0, NULL);
1291 #ifdef EXTENDED_ODBC_STORAGE
1292                 SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxuser), 0, (void *)mailboxuser, 0, NULL);
1293                 SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxcontext), 0, (void *)mailboxcontext, 0, NULL);
1294                 if (!ast_strlen_zero(category))
1295                         SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(category), 0, (void *)category, 0, NULL);
1296 #else
1297                 if (!ast_strlen_zero(category))
1298                         SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(category), 0, (void *)category, 0, NULL);
1299 #endif
1300                 res = odbc_smart_execute(obj, stmt);
1301                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1302                         ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1303                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1304                         goto yuck;
1305                 }
1306                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1307         } else
1308                 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1309 yuck:   
1310         if (cfg)
1311                 ast_config_destroy(cfg);
1312         if (fdm)
1313                 munmap(fdm, fdlen);
1314         if (fd > -1)
1315                 close(fd);
1316         return x;
1317 }
1318
1319 static void rename_file(char *sdir, int smsg, char *mailboxuser, char *mailboxcontext, char *ddir, int dmsg)
1320 {
1321         int res;
1322         SQLHSTMT stmt;
1323         char sql[256];
1324         char msgnums[20];
1325         char msgnumd[20];
1326         odbc_obj *obj;
1327
1328         delete_file(ddir, dmsg);
1329         obj = fetch_odbc_obj(odbc_database, 0);
1330         if (obj) {
1331                 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1332                 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
1333                 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1334                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1335                         ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1336                         goto yuck;
1337                 }
1338 #ifdef EXTENDED_ODBC_STORAGE
1339                 snprintf(sql, sizeof(sql), "UPDATE %s SET dir=?, msgnum=?, mailboxuser=?, mailboxcontext=? WHERE dir=? AND msgnum=?",odbc_table);
1340 #else
1341                 snprintf(sql, sizeof(sql), "UPDATE %s SET dir=?, msgnum=? WHERE dir=? AND msgnum=?",odbc_table);
1342 #endif
1343                 res = SQLPrepare(stmt, sql, SQL_NTS);
1344                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1345                         ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1346                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1347                         goto yuck;
1348                 }
1349                 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(ddir), 0, (void *)ddir, 0, NULL);
1350                 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnumd), 0, (void *)msgnumd, 0, NULL);
1351 #ifdef EXTENDED_ODBC_STORAGE
1352                 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxuser), 0, (void *)mailboxuser, 0, NULL);
1353                 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxcontext), 0, (void *)mailboxcontext, 0, NULL);
1354                 SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(sdir), 0, (void *)sdir, 0, NULL);
1355                 SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1356 #else
1357                 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(sdir), 0, (void *)sdir, 0, NULL);
1358                 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1359 #endif           
1360                 res = odbc_smart_execute(obj, stmt);
1361                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1362                         ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1363                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1364                         goto yuck;
1365                 }
1366                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1367         } else
1368                 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1369 yuck:
1370         return; 
1371 }
1372
1373 #else
1374
1375 static int count_messages(struct ast_vm_user *vmu, char *dir)
1376 {
1377         /* Find all .txt files - even if they are not in sequence from 0000 */
1378
1379         int vmcount = 0;
1380         DIR *vmdir = NULL;
1381         struct dirent *vment = NULL;
1382
1383         if (vm_lock_path(dir))
1384                 return ERROR_LOCK_PATH;
1385
1386         if ((vmdir = opendir(dir))) {
1387                 while ((vment = readdir(vmdir))) {
1388                         if (strlen(vment->d_name) > 7 && !strncmp(vment->d_name + 7, ".txt", 4)) 
1389                                 vmcount++;
1390                 }
1391                 closedir(vmdir);
1392         }
1393         ast_unlock_path(dir);
1394         
1395         return vmcount;
1396 }
1397
1398 static void rename_file(char *sfn, char *dfn)
1399 {
1400         char stxt[256];
1401         char dtxt[256];
1402         ast_filerename(sfn,dfn,NULL);
1403         snprintf(stxt, sizeof(stxt), "%s.txt", sfn);
1404         snprintf(dtxt, sizeof(dtxt), "%s.txt", dfn);
1405         rename(stxt, dtxt);
1406 }
1407
1408 static int copy(char *infile, char *outfile)
1409 {
1410         int ifd;
1411         int ofd;
1412         int res;
1413         int len;
1414         char buf[4096];
1415
1416 #ifdef HARDLINK_WHEN_POSSIBLE
1417         /* Hard link if possible; saves disk space & is faster */
1418         if (link(infile, outfile)) {
1419 #endif
1420                 if ((ifd = open(infile, O_RDONLY)) < 0) {
1421                         ast_log(LOG_WARNING, "Unable to open %s in read-only mode\n", infile);
1422                         return -1;
1423                 }
1424                 if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, VOICEMAIL_FILE_MODE)) < 0) {
1425                         ast_log(LOG_WARNING, "Unable to open %s in write-only mode\n", outfile);
1426                         close(ifd);
1427                         return -1;
1428                 }
1429                 do {
1430                         len = read(ifd, buf, sizeof(buf));
1431                         if (len < 0) {
1432                                 ast_log(LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
1433                                 close(ifd);
1434                                 close(ofd);
1435                                 unlink(outfile);
1436                         }
1437                         if (len) {
1438                                 res = write(ofd, buf, len);
1439                                 if (errno == ENOMEM || errno == ENOSPC || res != len) {
1440                                         ast_log(LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno));
1441                                         close(ifd);
1442                                         close(ofd);
1443                                         unlink(outfile);
1444                                 }
1445                         }
1446                 } while (len);
1447                 close(ifd);
1448                 close(ofd);
1449                 return 0;
1450 #ifdef HARDLINK_WHEN_POSSIBLE
1451         } else {
1452                 /* Hard link succeeded */
1453                 return 0;
1454         }
1455 #endif
1456 }
1457
1458 static void copy_file(char *frompath, char *topath)
1459 {
1460         char frompath2[256],topath2[256];
1461         ast_filecopy(frompath, topath, NULL);
1462         snprintf(frompath2, sizeof(frompath2), "%s.txt", frompath);
1463         snprintf(topath2, sizeof(topath2), "%s.txt", topath);
1464         copy(frompath2, topath2);
1465 }
1466
1467 /*
1468  * A negative return value indicates an error.
1469  */
1470 static int last_message_index(struct ast_vm_user *vmu, char *dir)
1471 {
1472         int x;
1473         char fn[256];
1474
1475         if (vm_lock_path(dir))
1476                 return ERROR_LOCK_PATH;
1477
1478         for (x = 0; x < vmu->maxmsg; x++) {
1479                 make_file(fn, sizeof(fn), dir, x);
1480                 if (ast_fileexists(fn, NULL, NULL) < 1)
1481                         break;
1482         }
1483         ast_unlock_path(dir);
1484
1485         return x - 1;
1486 }
1487
1488 static int vm_delete(char *file)
1489 {
1490         char *txt;
1491         int txtsize = 0;
1492
1493         txtsize = (strlen(file) + 5)*sizeof(char);
1494         txt = alloca(txtsize);
1495         /* Sprintf here would safe because we alloca'd exactly the right length,
1496          * but trying to eliminate all sprintf's anyhow
1497          */
1498         snprintf(txt, txtsize, "%s.txt", file);
1499         unlink(txt);
1500         return ast_filedelete(file, NULL);
1501 }
1502
1503
1504 #endif
1505 static int
1506 inbuf(struct baseio *bio, FILE *fi)
1507 {
1508         int l;
1509
1510         if (bio->ateof)
1511                 return 0;
1512
1513         if ((l = fread(bio->iobuf,1,BASEMAXINLINE,fi)) <= 0) {
1514                 if (ferror(fi))
1515                         return -1;
1516
1517                 bio->ateof = 1;
1518                 return 0;
1519         }
1520
1521         bio->iolen= l;
1522         bio->iocp= 0;
1523
1524         return 1;
1525 }
1526
1527 static int 
1528 inchar(struct baseio *bio, FILE *fi)
1529 {
1530         if (bio->iocp>=bio->iolen) {
1531                 if (!inbuf(bio, fi))
1532                         return EOF;
1533         }
1534
1535         return bio->iobuf[bio->iocp++];
1536 }
1537
1538 static int
1539 ochar(struct baseio *bio, int c, FILE *so)
1540 {
1541         if (bio->linelength>=BASELINELEN) {
1542                 if (fputs(eol,so)==EOF)
1543                         return -1;
1544
1545                 bio->linelength= 0;
1546         }
1547
1548         if (putc(((unsigned char)c),so)==EOF)
1549                 return -1;
1550
1551         bio->linelength++;
1552
1553         return 1;
1554 }
1555
1556 static int base_encode(char *filename, FILE *so)
1557 {
1558         unsigned char dtable[BASEMAXINLINE];
1559         int i,hiteof= 0;
1560         FILE *fi;
1561         struct baseio bio;
1562
1563         memset(&bio, 0, sizeof(bio));
1564         bio.iocp = BASEMAXINLINE;
1565
1566         if (!(fi = fopen(filename, "rb"))) {
1567                 ast_log(LOG_WARNING, "Failed to open log file: %s: %s\n", filename, strerror(errno));
1568                 return -1;
1569         }
1570
1571         for (i= 0;i<9;i++) {
1572                 dtable[i]= 'A'+i;
1573                 dtable[i+9]= 'J'+i;
1574                 dtable[26+i]= 'a'+i;
1575                 dtable[26+i+9]= 'j'+i;
1576         }
1577         for (i= 0;i<8;i++) {
1578                 dtable[i+18]= 'S'+i;
1579                 dtable[26+i+18]= 's'+i;
1580         }
1581         for (i= 0;i<10;i++) {
1582                 dtable[52+i]= '0'+i;
1583         }
1584         dtable[62]= '+';
1585         dtable[63]= '/';
1586
1587         while (!hiteof){
1588                 unsigned char igroup[3],ogroup[4];
1589                 int c,n;
1590
1591                 igroup[0]= igroup[1]= igroup[2]= 0;
1592
1593                 for (n= 0;n<3;n++) {
1594                         if ((c = inchar(&bio, fi)) == EOF) {
1595                                 hiteof= 1;
1596                                 break;
1597                         }
1598
1599                         igroup[n]= (unsigned char)c;
1600                 }
1601
1602                 if (n> 0) {
1603                         ogroup[0]= dtable[igroup[0]>>2];
1604                         ogroup[1]= dtable[((igroup[0]&3)<<4)|(igroup[1]>>4)];
1605                         ogroup[2]= dtable[((igroup[1]&0xF)<<2)|(igroup[2]>>6)];
1606                         ogroup[3]= dtable[igroup[2]&0x3F];
1607
1608                         if (n<3) {
1609                                 ogroup[3]= '=';
1610
1611                                 if (n<2)
1612                                         ogroup[2]= '=';
1613                         }
1614
1615                         for (i= 0;i<4;i++)
1616                                 ochar(&bio, ogroup[i], so);
1617                 }
1618         }
1619
1620         if (fputs(eol,so)==EOF)
1621                 return 0;
1622
1623         fclose(fi);
1624
1625         return 1;
1626 }
1627
1628 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)
1629 {
1630         char callerid[256];
1631         /* Prepare variables for substition in email body and subject */
1632         pbx_builtin_setvar_helper(ast, "VM_NAME", vmu->fullname);
1633         pbx_builtin_setvar_helper(ast, "VM_DUR", dur);
1634         snprintf(passdata, passdatasize, "%d", msgnum);
1635         pbx_builtin_setvar_helper(ast, "VM_MSGNUM", passdata);
1636         pbx_builtin_setvar_helper(ast, "VM_CONTEXT", context);
1637         pbx_builtin_setvar_helper(ast, "VM_MAILBOX", mailbox);
1638         pbx_builtin_setvar_helper(ast, "VM_CALLERID", ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, "Unknown Caller"));
1639         pbx_builtin_setvar_helper(ast, "VM_CIDNAME", (cidname ? cidname : "an unknown caller"));
1640         pbx_builtin_setvar_helper(ast, "VM_CIDNUM", (cidnum ? cidnum : "an unknown caller"));
1641         pbx_builtin_setvar_helper(ast, "VM_DATE", date);
1642         pbx_builtin_setvar_helper(ast, "VM_CATEGORY", category ? ast_strdupa(category) : "no category");
1643 }
1644
1645 /*
1646  * fill in *tm for current time according to the proper timezone, if any.
1647  * Return tm so it can be used as a function argument.
1648  */
1649 static const struct tm *vmu_tm(const struct ast_vm_user *vmu, struct tm *tm)
1650 {
1651         const struct vm_zone *z = NULL;
1652         time_t t = time(NULL);
1653
1654         /* Does this user have a timezone specified? */
1655         if (!ast_strlen_zero(vmu->zonetag)) {
1656                 /* Find the zone in the list */
1657                 AST_LIST_LOCK(&zones);
1658                 AST_LIST_TRAVERSE(&zones, z, list) {
1659                         if (!strcmp(z->name, vmu->zonetag))
1660                                 break;
1661                 }
1662                 AST_LIST_UNLOCK(&zones);
1663         }
1664         ast_localtime(&t, tm, z ? z->timezone : NULL);
1665         return tm;
1666 }
1667
1668 /* same as mkstemp, but return a FILE * */
1669 static FILE *vm_mkftemp(char *template)
1670 {
1671         FILE *p = NULL;
1672         int pfd = mkstemp(template);
1673         if (pfd > -1) {
1674                 p = fdopen(pfd, "w");
1675                 if (!p) {
1676                         close(pfd);
1677                         pfd = -1;
1678                 }
1679         }
1680         return p;
1681 }
1682
1683 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, const char *category)
1684 {
1685         FILE *p=NULL;
1686         char date[256];
1687         char host[MAXHOSTNAMELEN] = "";
1688         char who[256];
1689         char bound[256];
1690         char fname[256];
1691         char dur[256];
1692         char tmp[80] = "/tmp/astmail-XXXXXX";
1693         char tmp2[256];
1694         struct tm tm;
1695
1696         if (vmu && ast_strlen_zero(vmu->email)) {
1697                 ast_log(LOG_WARNING, "E-mail address missing for mailbox [%s].  E-mail will not be sent.\n", vmu->mailbox);
1698                 return(0);
1699         }
1700         if (!strcmp(format, "wav49"))
1701                 format = "WAV";
1702         ast_log(LOG_DEBUG, "Attaching file '%s', format '%s', uservm is '%d', global is %d\n", attach, format, attach_user_voicemail, ast_test_flag((&globalflags), VM_ATTACH));
1703         /* Make a temporary file instead of piping directly to sendmail, in case the mail
1704            command hangs */
1705         if ((p = vm_mkftemp(tmp)) == NULL) {
1706                 ast_log(LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
1707                 return -1;
1708         } else {
1709                 gethostname(host, sizeof(host)-1);
1710                 if (strchr(srcemail, '@'))
1711                         ast_copy_string(who, srcemail, sizeof(who));
1712                 else {
1713                         snprintf(who, sizeof(who), "%s@%s", srcemail, host);
1714                 }
1715                 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
1716                 strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
1717                 fprintf(p, "Date: %s\n", date);
1718
1719                 /* Set date format for voicemail mail */
1720                 strftime(date, sizeof(date), emaildateformat, &tm);
1721
1722                 if (*fromstring) {
1723                         struct ast_channel *ast;
1724                         if ((ast = ast_channel_alloc(0))) {
1725                                 char *passdata;
1726                                 int vmlen = strlen(fromstring)*3 + 200;
1727                                 if ((passdata = alloca(vmlen))) {
1728                                         memset(passdata, 0, vmlen);
1729                                         prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
1730                                         pbx_substitute_variables_helper(ast,fromstring,passdata,vmlen);
1731                                         fprintf(p, "From: %s <%s>\n",passdata,who);
1732                                 } else ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1733                                 ast_channel_free(ast);
1734                         } else ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
1735                 } else
1736                         fprintf(p, "From: Asterisk PBX <%s>\n", who);
1737                 fprintf(p, "To: %s <%s>\n", vmu->fullname, vmu->email);
1738
1739                 if (emailsubject) {
1740                         struct ast_channel *ast;
1741                         if ((ast = ast_channel_alloc(0))) {
1742                                 char *passdata;
1743                                 int vmlen = strlen(emailsubject)*3 + 200;
1744                                 if ((passdata = alloca(vmlen))) {
1745                                         memset(passdata, 0, vmlen);
1746                                         prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
1747                                         pbx_substitute_variables_helper(ast,emailsubject,passdata,vmlen);
1748                                         fprintf(p, "Subject: %s\n",passdata);
1749                                 } else ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1750                                 ast_channel_free(ast);
1751                         } else ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
1752                 } else
1753                 if (*emailtitle) {
1754                         fprintf(p, emailtitle, msgnum + 1, mailbox) ;
1755                         fprintf(p,"\n") ;
1756                 } else if (ast_test_flag((&globalflags), VM_PBXSKIP))
1757                         fprintf(p, "Subject: New message %d in mailbox %s\n", msgnum + 1, mailbox);
1758                 else
1759                         fprintf(p, "Subject: [PBX]: New message %d in mailbox %s\n", msgnum + 1, mailbox);
1760                 fprintf(p, "Message-ID: <Asterisk-%d-%d-%s-%d@%s>\n", msgnum, (unsigned int)ast_random(), mailbox, getpid(), host);
1761                 fprintf(p, "MIME-Version: 1.0\n");
1762                 if (attach_user_voicemail) {
1763                         /* Something unique. */
1764                         snprintf(bound, sizeof(bound), "voicemail_%d%s%d%d", msgnum, mailbox, getpid(), (unsigned int)ast_random());
1765
1766                         fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"\n\n\n", bound);
1767
1768                         fprintf(p, "--%s\n", bound);
1769                 }
1770                 fprintf(p, "Content-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n", charset);
1771                 if (emailbody) {
1772                         struct ast_channel *ast;
1773                         if ((ast = ast_channel_alloc(0))) {
1774                                 char *passdata;
1775                                 int vmlen = strlen(emailbody)*3 + 200;
1776                                 if ((passdata = alloca(vmlen))) {
1777                                         memset(passdata, 0, vmlen);
1778                                         prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
1779                                         pbx_substitute_variables_helper(ast,emailbody,passdata,vmlen);
1780                                         fprintf(p, "%s\n",passdata);
1781                                 } else ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1782                                 ast_channel_free(ast);
1783                         } else ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
1784                 } else {
1785                         fprintf(p, "Dear %s:\n\n\tJust wanted to let you know you were just left a %s long message (number %d)\n"
1786
1787                         "in mailbox %s from %s, on %s so you might\n"
1788                         "want to check it when you get a chance.  Thanks!\n\n\t\t\t\t--Asterisk\n\n", vmu->fullname, 
1789                         dur, msgnum + 1, mailbox, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
1790                 }
1791                 if (attach_user_voicemail) {
1792                         /* Eww. We want formats to tell us their own MIME type */
1793                         char *ctype = "audio/x-";
1794                         if (!strcasecmp(format, "ogg"))
1795                                 ctype = "application/";
1796                 
1797                         fprintf(p, "--%s\n", bound);
1798                         fprintf(p, "Content-Type: %s%s; name=\"msg%04d.%s\"\n", ctype, format, msgnum, format);
1799                         fprintf(p, "Content-Transfer-Encoding: base64\n");
1800                         fprintf(p, "Content-Description: Voicemail sound attachment.\n");
1801                         fprintf(p, "Content-Disposition: attachment; filename=\"msg%04d.%s\"\n\n", msgnum, format);
1802
1803                         snprintf(fname, sizeof(fname), "%s.%s", attach, format);
1804                         base_encode(fname, p);
1805                         fprintf(p, "\n\n--%s--\n.\n", bound);
1806                 }
1807                 fclose(p);
1808                 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
1809                 ast_safe_system(tmp2);
1810                 ast_log(LOG_DEBUG, "Sent mail to %s with command '%s'\n", vmu->email, mailcmd);
1811         }
1812         return 0;
1813 }
1814
1815 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)
1816 {
1817         char date[256];
1818         char host[MAXHOSTNAMELEN]="";
1819         char who[256];
1820         char dur[256];
1821         char tmp[80] = "/tmp/astmail-XXXXXX";
1822         char tmp2[256];
1823         struct tm tm;
1824         FILE *p;
1825
1826         if ((p = vm_mkftemp(tmp)) == NULL) {
1827                 ast_log(LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
1828                 return -1;
1829         } else {
1830                 gethostname(host, sizeof(host)-1);
1831                 if (strchr(srcemail, '@'))
1832                         ast_copy_string(who, srcemail, sizeof(who));
1833                 else {
1834                         snprintf(who, sizeof(who), "%s@%s", srcemail, host);
1835                 }
1836                 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
1837                 strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
1838                 fprintf(p, "Date: %s\n", date);
1839
1840                 if (*pagerfromstring) {
1841                         struct ast_channel *ast;
1842                         if ((ast = ast_channel_alloc(0))) {
1843                                 char *passdata;
1844                                 int vmlen = strlen(fromstring)*3 + 200;
1845                                 if ((passdata = alloca(vmlen))) {
1846                                         memset(passdata, 0, vmlen);
1847                                         prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
1848                                         pbx_substitute_variables_helper(ast,pagerfromstring,passdata,vmlen);
1849                                         fprintf(p, "From: %s <%s>\n",passdata,who);
1850                                 } else 
1851                                         ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1852                                 ast_channel_free(ast);
1853                         } else ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
1854                 } else
1855                         fprintf(p, "From: Asterisk PBX <%s>\n", who);
1856                 fprintf(p, "To: %s\n", pager);
1857                if (pagersubject) {
1858                        struct ast_channel *ast;
1859                        if ((ast = ast_channel_alloc(0))) {
1860                                char *passdata;
1861                                int vmlen = strlen(pagersubject)*3 + 200;
1862                                if ((passdata = alloca(vmlen))) {
1863                                        memset(passdata, 0, vmlen);
1864                                        prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
1865                                        pbx_substitute_variables_helper(ast,pagersubject,passdata,vmlen);
1866                                        fprintf(p, "Subject: %s\n\n",passdata);
1867                                } else ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1868                                ast_channel_free(ast);
1869                        } else ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
1870                } else
1871                        fprintf(p, "Subject: New VM\n\n");
1872                 strftime(date, sizeof(date), "%A, %B %d, %Y at %r", &tm);
1873                if (pagerbody) {
1874                        struct ast_channel *ast;
1875                        if ((ast = ast_channel_alloc(0))) {
1876                                char *passdata;
1877                                int vmlen = strlen(pagerbody)*3 + 200;
1878                                if ((passdata = alloca(vmlen))) {
1879                                        memset(passdata, 0, vmlen);
1880                                        prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
1881                                        pbx_substitute_variables_helper(ast,pagerbody,passdata,vmlen);
1882                                        fprintf(p, "%s\n",passdata);
1883                                } else ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1884                                ast_channel_free(ast);
1885                        } else ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
1886                } else {
1887                        fprintf(p, "New %s long msg in box %s\n"
1888                                        "from %s, on %s", dur, mailbox, (cidname ? cidname : (cidnum ? cidnum : "unknown")), date);
1889                }
1890                 fclose(p);
1891                 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
1892                 ast_safe_system(tmp2);
1893                 ast_log(LOG_DEBUG, "Sent page to %s with command '%s'\n", pager, mailcmd);
1894         }
1895         return 0;
1896 }
1897
1898 static int get_date(char *s, int len)
1899 {
1900         struct tm tm;
1901         time_t t;
1902         t = time(0);
1903         localtime_r(&t,&tm);
1904         return strftime(s, len, "%a %b %e %r %Z %Y", &tm);
1905 }
1906
1907 static int invent_message(struct ast_channel *chan, char *context, char *ext, int busy, char *ecodes)
1908 {
1909         int res;
1910         char fn[256];
1911         snprintf(fn, sizeof(fn), "%s%s/%s/greet", VM_SPOOL_DIR, context, ext);
1912         RETRIEVE(fn, -1);
1913         if (ast_fileexists(fn, NULL, NULL) > 0) {
1914                 res = ast_stream_and_wait(chan, fn, chan->language, ecodes);
1915                 if (res) {
1916                         DISPOSE(fn, -1);
1917                         return res;
1918                 }
1919         } else {
1920                 /* Dispose just in case */
1921                 DISPOSE(fn, -1);
1922                 res = ast_stream_and_wait(chan, "vm-theperson", chan->language, ecodes);
1923                 if (res)
1924                         return res;
1925                 res = ast_say_digit_str(chan, ext, ecodes, chan->language);
1926                 if (res)
1927                         return res;
1928         }
1929         res = ast_stream_and_wait(chan, busy ? "vm-isonphone" : "vm-isunavail", chan->language, ecodes);
1930         return res;
1931 }
1932
1933 static void free_user(struct ast_vm_user *vmu)
1934 {
1935         if (ast_test_flag(vmu, VM_ALLOCED))
1936                 free(vmu);
1937 }
1938
1939 static void free_zone(struct vm_zone *z)
1940 {
1941         free(z);
1942 }
1943
1944 static const char *mbox(int id)
1945 {
1946         static const char *msgs[] = {
1947                 "INBOX",
1948                 "Old",
1949                 "Work",   
1950                 "Family",
1951                 "Friends",
1952                 "Cust1",
1953                 "Cust2",
1954                 "Cust3",
1955                 "Cust4",  
1956                 "Cust5",
1957         };
1958         return (id >= 0 && id < (sizeof(msgs)/sizeof(msgs[0]))) ? msgs[id] : "Unknown";
1959 }
1960
1961 #ifdef USE_ODBC_STORAGE
1962 static int messagecount(const char *mailbox, int *newmsgs, int *oldmsgs)
1963 {
1964         int x = 0;
1965         int res;
1966         SQLHSTMT stmt;
1967         char sql[256];
1968         char rowdata[20];
1969         char tmp[256]="";
1970         char *context;
1971
1972         if (newmsgs)
1973                 *newmsgs = 0;
1974         if (oldmsgs)
1975                 *oldmsgs = 0;
1976         /* If no mailbox, return immediately */
1977         if (ast_strlen_zero(mailbox))
1978                 return 0;
1979
1980         ast_copy_string(tmp, mailbox, sizeof(tmp));
1981         
1982         context = strchr(tmp, '@');
1983         if (context) {   
1984                 *context = '\0';
1985                 context++;
1986         } else  
1987                 context = "default";
1988         
1989         odbc_obj *obj;
1990         obj = fetch_odbc_obj(odbc_database, 0);
1991         if (obj) {
1992                 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1993                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1994                         ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1995                         goto yuck;
1996                 }
1997                 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir LIKE '%%%s/%s/%s'", odbc_table, context, tmp, "INBOX");
1998                 res = SQLPrepare(stmt, sql, SQL_NTS);
1999                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2000                         ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
2001                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2002                         goto yuck;
2003                 }
2004                 res = odbc_smart_execute(obj, stmt);
2005                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2006                         ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2007                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2008                         goto yuck;
2009                 }
2010                 res = SQLFetch(stmt);
2011                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2012                         ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2013                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2014                         goto yuck;
2015                 }
2016                 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2017                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2018                         ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2019                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2020                         goto yuck;
2021                 }
2022                 *newmsgs = atoi(rowdata);
2023                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2024
2025                 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
2026                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2027                         ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
2028                         goto yuck;
2029                 }
2030                 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir like '%%%s/%s/%s'", odbc_table, context, tmp, "Old");
2031                 res = SQLPrepare(stmt, sql, SQL_NTS);
2032                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2033                         ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
2034                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2035                         goto yuck;
2036                 }
2037                 res = odbc_smart_execute(obj, stmt);
2038                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2039                         ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2040                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2041                         goto yuck;
2042                 }
2043                 res = SQLFetch(stmt);
2044                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2045                         ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2046                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2047                         goto yuck;
2048                 }
2049                 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2050                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2051                         ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2052                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2053                         goto yuck;
2054                 }
2055                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2056                 *oldmsgs = atoi(rowdata);
2057                 x = 1;
2058         } else
2059                 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2060                 
2061 yuck:   
2062         return x;
2063 }
2064
2065 static int has_voicemail(const char *mailbox, const char *folder)
2066 {
2067         int nummsgs = 0;
2068         int res;
2069         SQLHSTMT stmt;
2070         char sql[256];
2071         char rowdata[20];
2072         char tmp[256]="";
2073         char *context;
2074         if (!folder)
2075                 folder = "INBOX";
2076         /* If no mailbox, return immediately */
2077         if (ast_strlen_zero(mailbox))
2078                 return 0;
2079
2080         ast_copy_string(tmp, mailbox, sizeof(tmp));
2081                         
2082         context = strchr(tmp, '@');
2083         if (context) {
2084                 *context = '\0';
2085                 context++;
2086         } else
2087                 context = "default";
2088
2089         odbc_obj *obj;
2090         obj = fetch_odbc_obj(odbc_database, 0);
2091         if (obj) {
2092                 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
2093                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2094                         ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
2095                         goto yuck;
2096                 }
2097                 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir like '%%%s/%s/%s'", odbc_table, context, tmp, "INBOX");
2098                 res = SQLPrepare(stmt, sql, SQL_NTS);
2099                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {  
2100                         ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
2101                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2102                         goto yuck;
2103                 }
2104                 res = odbc_smart_execute(obj, stmt);
2105                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2106                         ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2107                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2108                         goto yuck;
2109                 }
2110                 res = SQLFetch(stmt);
2111                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2112                         ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2113                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2114                         goto yuck;
2115                 }
2116                 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2117                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2118                         ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2119                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2120                         goto yuck;
2121                 }
2122                 nummsgs = atoi(rowdata);
2123                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2124        } else
2125                 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2126
2127 yuck:
2128         if (nummsgs>=1)
2129                 return 1;
2130         else
2131                 return 0;
2132 }
2133
2134 #else
2135
2136 static int has_voicemail(const char *mailbox, const char *folder)
2137 {
2138         DIR *dir;
2139         struct dirent *de;
2140         char fn[256];
2141         char tmp[256]="";
2142         char *mb, *cur;
2143         char *context;
2144         int ret;
2145         if (!folder)
2146                 folder = "INBOX";
2147         /* If no mailbox, return immediately */
2148         if (ast_strlen_zero(mailbox))
2149                 return 0;
2150         if (strchr(mailbox, ',')) {
2151                 ast_copy_string(tmp, mailbox, sizeof(tmp));
2152                 mb = tmp;
2153                 ret = 0;
2154                 while((cur = strsep(&mb, ","))) {
2155                         if (!ast_strlen_zero(cur)) {
2156                                 if (has_voicemail(cur, folder))
2157                                         return 1; 
2158                         }
2159                 }
2160                 return 0;
2161         }
2162         ast_copy_string(tmp, mailbox, sizeof(tmp));
2163         context = strchr(tmp, '@');
2164         if (context) {
2165                 *context = '\0';
2166                 context++;
2167         } else
2168                 context = "default";
2169         snprintf(fn, sizeof(fn), "%s/%s/%s/%s", VM_SPOOL_DIR, context, tmp, folder);
2170         dir = opendir(fn);
2171         if (!dir)
2172                 return 0;
2173         while ((de = readdir(dir))) {
2174                 if (!strncasecmp(de->d_name, "msg", 3))
2175                         break;
2176         }
2177         closedir(dir);
2178         if (de)
2179                 return 1;
2180         return 0;
2181 }
2182
2183
2184 static int messagecount(const char *mailbox, int *newmsgs, int *oldmsgs)
2185 {
2186         DIR *dir;
2187         struct dirent *de;
2188         char fn[256];
2189         char tmp[256];
2190         char *context;
2191
2192         if (newmsgs)
2193                 *newmsgs = 0;
2194         if (oldmsgs)
2195                 *oldmsgs = 0;
2196         /* If no mailbox, return immediately */
2197         if (ast_strlen_zero(mailbox))
2198                 return 0;
2199         if (strchr(mailbox, ',')) {
2200                 int tmpnew, tmpold;
2201                 char *mb, *cur;
2202
2203                 ast_copy_string(tmp, mailbox, sizeof(tmp));
2204                 mb = tmp;
2205                 while((cur = strsep(&mb, ", "))) {
2206                         if (!ast_strlen_zero(cur)) {
2207                                 if (messagecount(cur, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2208                                         return -1;
2209                                 else {
2210                                         if (newmsgs)
2211                                                 *newmsgs += tmpnew; 
2212                                         if (oldmsgs)
2213                                                 *oldmsgs += tmpold;
2214                                 }
2215                         }
2216                 }
2217                 return 0;
2218         }
2219         ast_copy_string(tmp, mailbox, sizeof(tmp));
2220         context = strchr(tmp, '@');
2221         if (context) {
2222                 *context = '\0';
2223                 context++;
2224         } else
2225                 context = "default";
2226         if (newmsgs) {
2227                 snprintf(fn, sizeof(fn), "%s/%s/%s/INBOX", VM_SPOOL_DIR, context, tmp);
2228                 dir = opendir(fn);
2229                 if (dir) {
2230                         while ((de = readdir(dir))) {
2231                                 if ((strlen(de->d_name) > 3) && !strncasecmp(de->d_name, "msg", 3) &&
2232                                         !strcasecmp(de->d_name + strlen(de->d_name) - 3, "txt"))
2233                                                 (*newmsgs)++;
2234                                         
2235                         }
2236                         closedir(dir);
2237                 }
2238         }
2239         if (oldmsgs) {
2240                 snprintf(fn, sizeof(fn), "%s/%s/%s/Old", VM_SPOOL_DIR, context, tmp);
2241                 dir = opendir(fn);
2242                 if (dir) {
2243                         while ((de = readdir(dir))) {
2244                                 if ((strlen(de->d_name) > 3) && !strncasecmp(de->d_name, "msg", 3) &&
2245                                         !strcasecmp(de->d_name + strlen(de->d_name) - 3, "txt"))
2246                                                 (*oldmsgs)++;
2247                                         
2248                         }
2249                         closedir(dir);
2250                 }
2251         }
2252         return 0;
2253 }
2254
2255 #endif
2256
2257 static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu, int msgnum, long duration, char *fmt, char *cidnum, char *cidname);
2258
2259 static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt)
2260 {
2261         char fromdir[256], todir[256], frompath[256], topath[256];
2262         const char *frombox = mbox(imbox);
2263         int recipmsgnum;
2264
2265         ast_log(LOG_NOTICE, "Copying message from %s@%s to %s@%s\n", vmu->mailbox, vmu->context, recip->mailbox, recip->context);
2266
2267         create_dirpath(todir, sizeof(todir), recip->context, recip->mailbox, "INBOX");
2268   
2269         make_dir(fromdir, sizeof(fromdir), vmu->context, vmu->mailbox, frombox);
2270         make_file(frompath, sizeof(frompath), fromdir, msgnum);
2271
2272         if (vm_lock_path(todir))
2273                 return ERROR_LOCK_PATH;
2274
2275         recipmsgnum = 0;
2276         do {
2277                 make_file(topath, sizeof(topath), todir, recipmsgnum);
2278                 if (!EXISTS(todir, recipmsgnum, topath, chan->language))
2279                         break;
2280                 recipmsgnum++;
2281         } while (recipmsgnum < recip->maxmsg);
2282         if (recipmsgnum < recip->maxmsg) {
2283                 COPY(fromdir, msgnum, todir, recipmsgnum, recip->mailbox, recip->context, frompath, topath);
2284         } else {
2285                 ast_log(LOG_ERROR, "Recipient mailbox %s@%s is full\n", recip->mailbox, recip->context);
2286         }
2287         ast_unlock_path(todir);
2288         notify_new_message(chan, recip, recipmsgnum, duration, fmt, chan->cid.cid_num, chan->cid.cid_name);
2289         
2290         return 0;
2291 }
2292
2293 static void run_externnotify(char *context, char *extension)
2294 {
2295         char arguments[255];
2296         char ext_context[256] = "";
2297         int newvoicemails = 0, oldvoicemails = 0;
2298 #ifdef WITH_SMDI
2299         struct ast_smdi_mwi_message *mwi_msg;
2300 #endif
2301
2302         if (!ast_strlen_zero(context))
2303                 snprintf(ext_context, sizeof(ext_context), "%s@%s", extension, context);
2304         else
2305                 ast_copy_string(ext_context, extension, sizeof(ext_context));
2306
2307 #ifdef WITH_SMDI
2308         if (!strcasecmp(externnotify, "smdi")) {
2309                 if (ast_app_has_voicemail(ext_context, NULL)) 
2310                         ast_smdi_mwi_set(smdi_iface, extension);
2311                 else
2312                         ast_smdi_mwi_unset(smdi_iface, extension);
2313
2314                 if ((mwi_msg = ast_smdi_mwi_message_wait(smdi_iface, SMDI_MWI_WAIT_TIMEOUT))) {
2315                         ast_log(LOG_ERROR, "Error executing SMDI MWI change for %s on %s\n", extension, smdi_iface->name);
2316                         if (!strncmp(mwi_msg->cause, "INV", 3))
2317                                 ast_log(LOG_ERROR, "Invalid MWI extension: %s\n", mwi_msg->fwd_st);
2318                         else if (!strncmp(mwi_msg->cause, "BLK", 3))
2319                                 ast_log(LOG_WARNING, "MWI light was already on or off for %s\n", mwi_msg->fwd_st);
2320                         ast_log(LOG_WARNING, "The switch reported '%s'\n", mwi_msg->cause);
2321                         ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
2322                 } else {
2323                         ast_log(LOG_DEBUG, "Successfully executed SMDI MWI change for %s on %s\n", extension, smdi_iface->name);
2324                 }
2325         } else if (!ast_strlen_zero(externnotify)) {
2326 #else
2327         if (!ast_strlen_zero(externnotify)) {
2328 #endif
2329                 if (messagecount(ext_context, &newvoicemails, &oldvoicemails)) {
2330                         ast_log(LOG_ERROR, "Problem in calculating number of voicemail messages available for extension %s\n", extension);
2331                 } else {
2332                         snprintf(arguments, sizeof(arguments), "%s %s %s %d&", externnotify, context, extension, newvoicemails);
2333                         ast_log(LOG_DEBUG, "Executing %s\n", arguments);
2334                         ast_safe_system(arguments);
2335                 }
2336         }
2337 }
2338
2339 struct leave_vm_options {
2340         unsigned int flags;
2341         signed char record_gain;
2342 };
2343
2344 static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_options *options)
2345 {
2346         char tmptxtfile[256], txtfile[256];
2347         char callerid[256];
2348         FILE *txt;
2349         int res = 0;
2350         int msgnum;
2351         int duration = 0;
2352         int ausemacro = 0;
2353         int ousemacro = 0;
2354         char date[256];
2355         char dir[256];
2356         char fn[256];
2357         char prefile[256]="";
2358         char tempfile[256]="";
2359         char ext_context[256] = "";
2360         char fmt[80];
2361         char *context;
2362         char ecodes[16] = "#";
2363         char tmp[256] = "", *tmpptr;
2364         struct ast_vm_user *vmu;
2365         struct ast_vm_user svm;
2366         const char *category = NULL;
2367
2368         ast_copy_string(tmp, ext, sizeof(tmp));
2369         ext = tmp;
2370         context = strchr(tmp, '@');
2371         if (context) {
2372                 *context++ = '\0';
2373                 tmpptr = strchr(context, '&');
2374         } else {
2375                 tmpptr = strchr(ext, '&');
2376         }
2377
2378         if (tmpptr)
2379                 *tmpptr++ = '\0';
2380
2381         category = pbx_builtin_getvar_helper(chan, "VM_CATEGORY");
2382
2383         if (!(vmu = find_user(&svm, context, ext))) {
2384                 ast_log(LOG_WARNING, "No entry in voicemail config file for '%s'\n", ext);
2385                 if (ast_test_flag(options, OPT_PRIORITY_JUMP) || ast_opt_priority_jumping)
2386                         ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
2387                 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
2388                 return res;
2389         }
2390
2391         /* Setup pre-file if appropriate */
2392         if (strcmp(vmu->context, "default"))
2393                 snprintf(ext_context, sizeof(ext_context), "%s@%s", ext, vmu->context);
2394         else
2395                 ast_copy_string(ext_context, vmu->context, sizeof(ext_context));
2396         if (ast_test_flag(options, OPT_BUSY_GREETING))
2397                 snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, ext);
2398         else if (ast_test_flag(options, OPT_UNAVAIL_GREETING))
2399                 snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, ext);
2400         snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", VM_SPOOL_DIR, vmu->context, ext);
2401         RETRIEVE(tempfile, -1);
2402         if (ast_fileexists(tempfile, NULL, NULL) > 0)
2403                 ast_copy_string(prefile, tempfile, sizeof(prefile));
2404         DISPOSE(tempfile, -1);
2405         /* It's easier just to try to make it than to check for its existence */
2406         create_dirpath(dir, sizeof(dir), vmu->context, ext, "INBOX");
2407
2408         /* Check current or macro-calling context for special extensions */
2409         if (!ast_strlen_zero(vmu->exit)) {
2410                 if (ast_exists_extension(chan, vmu->exit, "o", 1, chan->cid.cid_num))
2411                         strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2412         } else if (ast_exists_extension(chan, chan->context, "o", 1, chan->cid.cid_num))
2413                 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2414         else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num)) {
2415                 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2416                 ousemacro = 1;
2417         }
2418
2419         if (!ast_strlen_zero(vmu->exit)) {
2420                 if (ast_exists_extension(chan, vmu->exit, "a", 1, chan->cid.cid_num))
2421                         strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
2422         } else if (ast_exists_extension(chan, chan->context, "a", 1, chan->cid.cid_num))
2423                 strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
2424         else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "a", 1, chan->cid.cid_num)) {
2425                 strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
2426                 ausemacro = 1;
2427         }
2428
2429         /* Play the beginning intro if desired */
2430         if (!ast_strlen_zero(prefile)) {
2431                 RETRIEVE(prefile, -1);
2432                 if (ast_fileexists(prefile, NULL, NULL) > 0) {
2433                         if (ast_streamfile(chan, prefile, chan->language) > -1) 
2434                                 res = ast_waitstream(chan, ecodes);
2435                 } else {
2436                         ast_log(LOG_DEBUG, "%s doesn't exist, doing what we can\n", prefile);
2437                         res = invent_message(chan, vmu->context, ext, ast_test_flag(options, OPT_BUSY_GREETING), ecodes);
2438                 }
2439                 DISPOSE(prefile, -1);
2440                 if (res < 0) {
2441                         ast_log(LOG_DEBUG, "Hang up during prefile playback\n");
2442                         free_user(vmu);
2443                         pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
2444                         return -1;
2445                 }
2446         }
2447         if (res == '#') {
2448                 /* On a '#' we skip the instructions */
2449                 ast_set_flag(options, OPT_SILENT);
2450                 res = 0;
2451         }
2452         if (!res && !ast_test_flag(options, OPT_SILENT)) {
2453                 res = ast_stream_and_wait(chan, INTRO, chan->language, ecodes);
2454                 if (res == '#') {
2455                         ast_set_flag(options, OPT_SILENT);
2456                         res = 0;
2457                 }
2458         }
2459         if (res > 0)
2460                 ast_stopstream(chan);
2461         /* Check for a '*' here in case the caller wants to escape from voicemail to something
2462            other than the operator -- an automated attendant or mailbox login for example */
2463         if (res == '*') {
2464                 chan->exten[0] = 'a';
2465                 chan->exten[1] = '\0';
2466                 if (!ast_strlen_zero(vmu->exit)) {
2467                         ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
2468                 } else if (ausemacro && !ast_strlen_zero(chan->macrocontext)) {
2469                         ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
2470                 }
2471                 chan->priority = 0;
2472                 free_user(vmu);
2473                 pbx_builtin_setvar_helper(chan, "VMSTATUS", "USEREXIT");
2474                 return 0;
2475         }
2476         /* Check for a '0' here */
2477         if (res == '0') {
2478         transfer:
2479                 if (ast_test_flag(vmu, VM_OPERATOR)) {
2480                         chan->exten[0] = 'o';
2481                         chan->exten[1] = '\0';
2482                         if (!ast_strlen_zero(vmu->exit)) {
2483                                 ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
2484                         } else if (ousemacro && !ast_strlen_zero(chan->macrocontext)) {
2485                                 ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
2486                         }
2487                         ast_play_and_wait(chan, "transfer");
2488                         chan->priority = 0;
2489                         free_user(vmu);
2490                         pbx_builtin_setvar_helper(chan, "VMSTATUS", "USEREXIT");
2491                         return 0;
2492                 } else {
2493                         ast_play_and_wait(chan, "vm-sorry");
2494                         pbx_builtin_setvar_helper(chan, "VMSTATUS", "USEREXIT");
2495                         return 0;
2496                 }
2497         }
2498         if (res < 0) {
2499                 free_user(vmu);
2500                 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
2501                 return -1;
2502         }
2503         /* The meat of recording the message...  All the announcements and beeps have been played*/
2504         ast_copy_string(fmt, vmfmts, sizeof(fmt));
2505         if (!ast_strlen_zero(fmt)) {
2506                 msgnum = 0;
2507
2508                 if (vm_lock_path(dir)) {
2509                         free_user(vmu);
2510                         return ERROR_LOCK_PATH;
2511                 }
2512
2513                 /* 
2514                  * This operation can be very expensive if done say over NFS or if the mailbox has 100+ messages
2515                  * in the folder.  So we should get this first so we don't cut off the first few seconds of the 
2516                  * message.  
2517                  */
2518                 do {
2519                         make_file(fn, sizeof(fn), dir, msgnum);
2520                         if (!EXISTS(dir,msgnum,fn,chan->language))
2521                                 break;
2522                         msgnum++;
2523                 } while (msgnum < vmu->maxmsg);
2524
2525                 /* Now play the beep once we have the message number for our next message. */
2526                 if (res >= 0) {
2527                         /* Unless we're *really* silent, try to send the beep */
2528                         res = ast_stream_and_wait(chan, "beep", chan->language, "");
2529                 }
2530                 if (msgnum < vmu->maxmsg) {
2531                         /* assign a variable with the name of the voicemail file */       
2532                         pbx_builtin_setvar_helper(chan, "VM_MESSAGEFILE", fn);
2533                                 
2534                         /* Store information */
2535                         snprintf(txtfile, sizeof(txtfile), "%s.txt", fn);
2536                         snprintf(tmptxtfile, sizeof(tmptxtfile), "%s.txt.tmp", fn);
2537                         txt = fopen(tmptxtfile, "w+");
2538                         if (txt) {
2539                                 get_date(date, sizeof(date));
2540                                 fprintf(txt, 
2541                                         ";\n"
2542                                         "; Message Information file\n"
2543                                         ";\n"
2544                                         "[message]\n"
2545                                         "origmailbox=%s\n"
2546                                         "context=%s\n"
2547                                         "macrocontext=%s\n"
2548                                         "exten=%s\n"
2549                                         "priority=%d\n"
2550                                         "callerchan=%s\n"
2551                                         "callerid=%s\n"
2552                                         "origdate=%s\n"
2553                                         "origtime=%ld\n"
2554                                         "category=%s\n",
2555                                         ext,
2556                                         chan->context,
2557                                         chan->macrocontext, 
2558                                         chan->exten,
2559                                         chan->priority,
2560                                         chan->name,
2561                                         ast_callerid_merge(callerid, sizeof(callerid), chan->cid.cid_name, chan->cid.cid_num, "Unknown"),
2562                                         date, (long)time(NULL),
2563                                         category ? category : ""); 
2564                         } else
2565                                 ast_log(LOG_WARNING, "Error opening text file for output\n");
2566                         res = play_record_review(chan, NULL, fn, vmmaxmessage, fmt, 1, vmu, &duration, dir, options->record_gain);
2567                         if (res == '0') {
2568                                 if (txt) {
2569                                         fclose(txt);
2570                                         rename(tmptxtfile, txtfile);
2571                                 }
2572                                 goto transfer;
2573                         }
2574                         if (res > 0)
2575                                 res = 0;
2576                         if (txt) {
2577                                 fprintf(txt, "duration=%d\n", duration);
2578                                 fclose(txt);
2579                                 rename(tmptxtfile, txtfile);
2580                         }
2581                                 
2582                         if (duration < vmminmessage) {
2583                                 if (option_verbose > 2) 
2584                                         ast_verbose( VERBOSE_PREFIX_3 "Recording was %d seconds long but needs to be at least %d - abandoning\n", duration, vmminmessage);
2585                                 DELETE(dir,msgnum,fn);
2586                                 /* XXX We should really give a prompt too short/option start again, with leave_vm_out called only after a timeout XXX */
2587                                 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
2588                                 goto leave_vm_out;
2589                         }
2590                         /* Are there to be more recipients of this message? */
2591                         while (tmpptr) {
2592                                 struct ast_vm_user recipu, *recip;
2593                                 char *exten, *context;
2594                                         
2595                                 exten = strsep(&tmpptr, "&");
2596                                 context = strchr(exten, '@');
2597                                 if (context) {
2598                                         *context = '\0';
2599                                         context++;
2600                                 }
2601                                 if ((recip = find_user(&recipu, context, exten))) {
2602                                         copy_message(chan, vmu, 0, msgnum, duration, recip, fmt);
2603                                         free_user(recip);
2604                                 }
2605                         }
2606                         if (ast_fileexists(fn, NULL, NULL)) {
2607                                 STORE(dir, vmu->mailbox, vmu->context, msgnum);
2608                                 notify_new_message(chan, vmu, msgnum, duration, fmt, chan->cid.cid_num, chan->cid.cid_name);
2609                                 DISPOSE(dir, msgnum);
2610                         }
2611                         pbx_builtin_setvar_helper(chan, "VMSTATUS", "SUCCESS");
2612                 } else {
2613                         ast_unlock_path(dir);
2614                         res = ast_stream_and_wait(chan, "vm-mailboxfull", chan->language, "");
2615                         ast_log(LOG_WARNING, "No more messages possible\n");
2616                         pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
2617                 }
2618         } else
2619                 ast_log(LOG_WARNING, "No format for saving voicemail?\n");
2620  leave_vm_out:
2621         free_user(vmu);
2622         
2623         return res;
2624 }
2625
2626 static int resequence_mailbox(struct ast_vm_user *vmu, char *dir)
2627 {
2628         /* we know max messages, so stop process when number is hit */
2629
2630         int x,dest;
2631         char sfn[256];
2632         char dfn[256];
2633
2634         if (vm_lock_path(dir))
2635                 return ERROR_LOCK_PATH;
2636
2637         for (x = 0, dest = 0; x < vmu->maxmsg; x++) {
2638                 make_file(sfn, sizeof(sfn), dir, x);
2639                 if (EXISTS(dir, x, sfn, NULL)) {
2640                         
2641                         if(x != dest) {
2642                                 make_file(dfn, sizeof(dfn), dir, dest);
2643                                 RENAME(dir, x, vmu->mailbox, vmu->context, dir, dest, sfn, dfn);
2644                         }
2645                         
2646                         dest++;
2647                 }
2648         }
2649         ast_unlock_path(dir);
2650
2651         return 0;
2652 }
2653
2654
2655 static int say_and_wait(struct ast_channel *chan, int num, const char *language)
2656 {
2657         int d;
2658         d = ast_say_number(chan, num, AST_DIGIT_ANY, language, (char *) NULL);
2659         return d;
2660 }
2661
2662 static int save_to_folder(struct ast_vm_user *vmu, char *dir, int msg, char *context, char *username, int box)
2663 {
2664         char sfn[256];
2665         char dfn[256];
2666         char ddir[256];
2667         const char *dbox = mbox(box);
2668         int x;
2669         make_file(sfn, sizeof(sfn), dir, msg);
2670         create_dirpath(ddir, sizeof(ddir), context, username, dbox);
2671
2672         if (vm_lock_path(ddir))
2673                 return ERROR_LOCK_PATH;
2674
2675         for (x = 0; x < vmu->maxmsg; x++) {
2676                 make_file(dfn, sizeof(dfn), ddir, x);
2677                 if (!EXISTS(ddir, x, dfn, NULL))
2678                         break;
2679         }
2680         if (x >= vmu->maxmsg) {
2681                 ast_unlock_path(ddir);
2682                 return -1;
2683         }
2684         if (strcmp(sfn, dfn)) {
2685                 COPY(dir, msg, ddir, x, username, context, sfn, dfn);
2686         }
2687         ast_unlock_path(ddir);
2688         
2689         return 0;
2690 }
2691
2692 static int adsi_logo(unsigned char *buf)
2693 {
2694         int bytes = 0;
2695         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 1, ADSI_JUST_CENT, 0, "Comedian Mail", "");
2696         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 2, ADSI_JUST_CENT, 0, "(C)2002 LSS, Inc.", "");
2697         return bytes;
2698 }
2699
2700 static int adsi_load_vmail(struct ast_channel *chan, int *useadsi)
2701 {
2702         unsigned char buf[256];
2703         int bytes=0;
2704         int x;
2705         char num[5];
2706
2707         *useadsi = 0;
2708         bytes += adsi_data_mode(buf + bytes);
2709         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
2710
2711         bytes = 0;
2712         bytes += adsi_logo(buf);
2713         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, "Downloading Scripts", "");
2714 #ifdef DISPLAY
2715         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_LEFT, 0, "   .", "");
2716 #endif
2717         bytes += adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
2718         bytes += adsi_data_mode(buf + bytes);
2719         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
2720
2721         if (adsi_begin_download(chan, addesc, adsifdn, adsisec, adsiver)) {
2722                 bytes = 0;
2723                 bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, "Load Cancelled.", "");
2724                 bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_CENT, 0, "ADSI Unavailable", "");
2725                 bytes += adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
2726                 bytes += adsi_voice_mode(buf + bytes, 0);
2727                 adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
2728                 return 0;
2729         }
2730
2731 #ifdef DISPLAY
2732         /* Add a dot */
2733         bytes = 0;
2734         bytes += adsi_logo(buf);
2735         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, "Downloading Scripts", "");
2736         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_LEFT, 0, "   ..", "");
2737         bytes += adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
2738         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
2739 #endif
2740         bytes = 0;
2741         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 0, "Listen", "Listen", "1", 1);
2742         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 1, "Folder", "Folder", "2", 1);
2743         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 2, "Advanced", "Advnced", "3", 1);
2744         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 3, "Options", "Options", "0", 1);
2745         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 4, "Help", "Help", "*", 1);
2746         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 5, "Exit", "Exit", "#", 1);
2747         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD);
2748
2749 #ifdef DISPLAY
2750         /* Add another dot */
2751         bytes = 0;
2752         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_LEFT, 0, "   ...", "");
2753         bytes += adsi_voice_mode(buf + bytes, 0);
2754
2755         bytes += adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
2756         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
2757 #endif
2758
2759         bytes = 0;
2760         /* These buttons we load but don't use yet */
2761         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 6, "Previous", "Prev", "4", 1);
2762         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 8, "Repeat", "Repeat", "5", 1);
2763         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 7, "Delete", "Delete", "7", 1);
2764         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 9, "Next", "Next", "6", 1);
2765         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 10, "Save", "Save", "9", 1);
2766         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 11, "Undelete", "Restore", "7", 1);
2767         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD);
2768
2769 #ifdef DISPLAY
2770         /* Add another dot */
2771         bytes = 0;
2772         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_LEFT, 0, "   ....", "");
2773         bytes += adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
2774         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
2775 #endif
2776
2777         bytes = 0;
2778         for (x=0;x<5;x++) {
2779                 snprintf(num, sizeof(num), "%d", x);
2780                 bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 12 + x, mbox(x), mbox(x), num, 1);
2781         }
2782         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 12 + 5, "Cancel", "Cancel", "#", 1);
2783         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD);
2784
2785 #ifdef DISPLAY
2786         /* Add another dot */
2787         bytes = 0;
2788         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_LEFT, 0, "   .....", "");
2789         bytes += adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
2790         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
2791 #endif
2792
2793         if (adsi_end_download(chan)) {
2794                 bytes = 0;
2795                 bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, "Download Unsuccessful.", "");
2796                 bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_CENT, 0, "ADSI Unavailable", "");
2797                 bytes += adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
2798                 bytes += adsi_voice_mode(buf + bytes, 0);
2799                 adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
2800                 return 0;
2801         }
2802         bytes = 0;
2803         bytes += adsi_download_disconnect(buf + bytes);
2804         bytes += adsi_voice_mode(buf + bytes, 0);
2805         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD);
2806
2807         ast_log(LOG_DEBUG, "Done downloading scripts...\n");
2808
2809 #ifdef DISPLAY
2810         /* Add last dot */
2811         bytes = 0;
2812         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_CENT, 0, "   ......", "");
2813         bytes += adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
2814 #endif
2815         ast_log(LOG_DEBUG, "Restarting session...\n");
2816
2817         bytes = 0;
2818         /* Load the session now */
2819         if (adsi_load_session(chan, adsifdn, adsiver, 1) == 1) {
2820                 *useadsi = 1;
2821                 bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, "Scripts Loaded!", "");
2822         } else
2823                 bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, "Load Failed!", "");
2824
2825         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
2826         return 0;
2827 }
2828
2829 static void adsi_begin(struct ast_channel *chan, int *useadsi)
2830 {
2831         int x;
2832         if (!adsi_available(chan))
2833                 return;
2834         x = adsi_load_session(chan, adsifdn, adsiver, 1);
2835         if (x < 0)
2836                 return;
2837         if (!x) {
2838                 if (adsi_load_vmail(chan, useadsi)) {
2839                         ast_log(LOG_WARNING, "Unable to upload voicemail scripts\n");
2840                         return;
2841                 }
2842         } else
2843                 *useadsi = 1;
2844 }
2845
2846 static void adsi_login(struct ast_channel *chan)
2847 {
2848         unsigned char buf[256];
2849         int bytes=0;
2850         unsigned char keys[8];
2851         int x;
2852         if (!adsi_available(chan))
2853                 return;
2854
2855         for (x=0;x<8;x++)
2856                 keys[x] = 0;
2857         /* Set one key for next */
2858         keys[3] = ADSI_KEY_APPS + 3;
2859
2860         bytes += adsi_logo(buf + bytes);
2861         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, " ", "");
2862         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_CENT, 0, " ", "");
2863         bytes += adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
2864         bytes += adsi_input_format(buf + bytes, 1, ADSI_DIR_FROM_LEFT, 0, "Mailbox: ******", "");
2865         bytes += adsi_input_control(buf + bytes, ADSI_COMM_PAGE, 4, 1, 1, ADSI_JUST_LEFT);
2866         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 3, "Enter", "Enter", "#", 1);
2867         bytes += adsi_set_keys(buf + bytes, keys);
2868         bytes += adsi_voice_mode(buf + bytes, 0);
2869         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
2870 }
2871
2872 static void adsi_password(struct ast_channel *chan)
2873 {
2874         unsigned char buf[256];
2875         int bytes=0;
2876         unsigned char keys[8];
2877         int x;
2878         if (!adsi_available(chan))
2879                 return;
2880
2881         for (x=0;x<8;x++)
2882                 keys[x] = 0;
2883         /* Set one key for next */
2884         keys[3] = ADSI_KEY_APPS + 3;
2885
2886         bytes += adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
2887         bytes += adsi_input_format(buf + bytes, 1, ADSI_DIR_FROM_LEFT, 0, "Password: ******", "");
2888         bytes += adsi_input_control(buf + bytes, ADSI_COMM_PAGE, 4, 0, 1, ADSI_JUST_LEFT);
2889         bytes += adsi_set_keys(buf + bytes, keys);
2890         bytes += adsi_voice_mode(buf + bytes, 0);
2891         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
2892 }
2893
2894 static void adsi_folders(struct ast_channel *chan, int start, char *label)
2895 {
2896         unsigned char buf[256];
2897         int bytes=0;
2898         unsigned char keys[8];
2899         int x,y;
2900
2901         if (!adsi_available(chan))
2902                 return;
2903
2904         for (x=0;x<5;x++) {
2905                 y = ADSI_KEY_APPS + 12 + start + x;
2906                 if (y > ADSI_KEY_APPS + 12 + 4)
2907                         y = 0;
2908                 keys[x] = ADSI_KEY_SKT | y;
2909         }
2910         keys[5] = ADSI_KEY_SKT | (ADSI_KEY_APPS + 17);
2911         keys[6] = 0;
2912         keys[7] = 0;
2913
2914         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 1, ADSI_JUST_CENT, 0, label, "");
2915         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 2, ADSI_JUST_CENT, 0, " ", "");
2916         bytes += adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
2917         bytes += adsi_set_keys(buf + bytes, keys);
2918         bytes += adsi_voice_mode(buf + bytes, 0);
2919
2920         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
2921 }
2922
2923 static void adsi_message(struct ast_channel *chan, struct vm_state *vms)
2924 {
2925         int bytes=0;
2926         unsigned char buf[256]; 
2927         char buf1[256], buf2[256];
2928         char fn2[256];
2929
2930         char cid[256]="";
2931         char *val;
2932         char *name, *num;
2933         char datetime[21]="";
2934         FILE *f;
2935
2936         unsigned char keys[8];
2937
2938         int x;
2939
2940         if (!adsi_available(chan))
2941                 return;
2942
2943         /* Retrieve important info */
2944         snprintf(fn2, sizeof(fn2), "%s.txt", vms->fn);
2945         f = fopen(fn2, "r");
2946         if (f) {
2947                 while (!feof(f)) {      
2948                         fgets((char *)buf, sizeof(buf), f);
2949                         if (!feof(f)) {
2950                                 char *stringp=NULL;
2951                                 stringp = (char *)buf;
2952                                 strsep(&stringp, "=");
2953                                 val = strsep(&stringp, "=");
2954                                 if (!ast_strlen_zero(val)) {
2955                                         if (!strcmp((char *)buf, "callerid"))
2956                                                 ast_copy_string(cid, val, sizeof(cid));
2957                                         if (!strcmp((char *)buf, "origdate"))
2958                                                 ast_copy_string(datetime, val, sizeof(datetime));
2959                                 }
2960                         }
2961                 }
2962                 fclose(f);
2963         }
2964         /* New meaning for keys */
2965         for (x=0;x<5;x++)
2966                 keys[x] = ADSI_KEY_SKT | (ADSI_KEY_APPS + 6 + x);
2967         keys[6] = 0x0;
2968         keys[7] = 0x0;
2969
2970         if (!vms->curmsg) {
2971                 /* No prev key, provide "Folder" instead */
2972                 keys[0] = ADSI_KEY_SKT | (ADSI_KEY_APPS + 1);
2973         }
2974         if (vms->curmsg >= vms->lastmsg) {
2975                 /* If last message ... */
2976                 if (vms->curmsg) {
2977                         /* but not only message, provide "Folder" instead */
2978                         keys[3] = ADSI_KEY_SKT | (ADSI_KEY_APPS + 1);
2979                         bytes += adsi_voice_mode(buf + bytes, 0);
2980
2981                 } else {
2982                         /* Otherwise if only message, leave blank */
2983                         keys[3] = 1;
2984                 }
2985         }
2986
2987         if (!ast_strlen_zero(cid)) {
2988                 ast_callerid_parse(cid, &name, &num);
2989                 if (!name)
2990                         name = num;
2991         } else
2992                 name = "Unknown Caller";
2993
2994         /* If deleted, show "undeleted" */
2995
2996         if (vms->deleted[vms->curmsg])
2997                 keys[1] = ADSI_KEY_SKT | (ADSI_KEY_APPS + 11);
2998
2999         /* Except "Exit" */
3000         keys[5] = ADSI_KEY_SKT | (ADSI_KEY_APPS + 5);
3001         snprintf(buf1, sizeof(buf1), "%s%s", vms->curbox,
3002                 strcasecmp(vms->curbox, "INBOX") ? " Messages" : "");
3003         snprintf(buf2, sizeof(buf2), "Message %d of %d", vms->curmsg + 1, vms->lastmsg + 1);
3004
3005         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 1, ADSI_JUST_LEFT, 0, buf1, "");
3006         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 2, ADSI_JUST_LEFT, 0, buf2, "");
3007         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_LEFT, 0, name, "");
3008         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_LEFT, 0, datetime, "");
3009         bytes += adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
3010         bytes += adsi_set_keys(buf + bytes, keys);
3011         bytes += adsi_voice_mode(buf + bytes, 0);
3012
3013         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
3014 }
3015
3016 static void adsi_delete(struct ast_channel *chan, struct vm_state *vms)
3017 {
3018         int bytes=0;
3019         unsigned char buf[256];
3020         unsigned char keys[8];
3021
3022         int x;
3023
3024         if (!adsi_available(chan))
3025                 return;
3026
3027         /* New meaning for keys */