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