Merged revisions 14276 via svnmerge from
[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 'unavailble 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, char *context, char *ext, 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, char *context, char *ext, 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_BINARY, 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         p = vm_mkftemp(tmp);
1701         if (p == NULL) {
1702                 ast_log(LOG_WARNING, "Unable to launch '%s'\n", mailcmd);
1703                 return -1;
1704         } else {
1705                 gethostname(host, sizeof(host)-1);
1706                 if (strchr(srcemail, '@'))
1707                         ast_copy_string(who, srcemail, sizeof(who));
1708                 else {
1709                         snprintf(who, sizeof(who), "%s@%s", srcemail, host);
1710                 }
1711                 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
1712                 strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
1713                 fprintf(p, "Date: %s\n", date);
1714
1715                 /* Set date format for voicemail mail */
1716                 strftime(date, sizeof(date), emaildateformat, &tm);
1717
1718                 if (*fromstring) {
1719                         struct ast_channel *ast;
1720                         if ((ast = ast_channel_alloc(0))) {
1721                                 char *passdata;
1722                                 int vmlen = strlen(fromstring)*3 + 200;
1723                                 if ((passdata = alloca(vmlen))) {
1724                                         memset(passdata, 0, vmlen);
1725                                         prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
1726                                         pbx_substitute_variables_helper(ast,fromstring,passdata,vmlen);
1727                                         fprintf(p, "From: %s <%s>\n",passdata,who);
1728                                 } else ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1729                                 ast_channel_free(ast);
1730                         } else ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
1731                 } else
1732                         fprintf(p, "From: Asterisk PBX <%s>\n", who);
1733                 fprintf(p, "To: %s <%s>\n", vmu->fullname, vmu->email);
1734
1735                 if (emailsubject) {
1736                         struct ast_channel *ast;
1737                         if ((ast = ast_channel_alloc(0))) {
1738                                 char *passdata;
1739                                 int vmlen = strlen(emailsubject)*3 + 200;
1740                                 if ((passdata = alloca(vmlen))) {
1741                                         memset(passdata, 0, vmlen);
1742                                         prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
1743                                         pbx_substitute_variables_helper(ast,emailsubject,passdata,vmlen);
1744                                         fprintf(p, "Subject: %s\n",passdata);
1745                                 } else ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1746                                 ast_channel_free(ast);
1747                         } else ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
1748                 } else
1749                 if (*emailtitle) {
1750                         fprintf(p, emailtitle, msgnum + 1, mailbox) ;
1751                         fprintf(p,"\n") ;
1752                 } else if (ast_test_flag((&globalflags), VM_PBXSKIP))
1753                         fprintf(p, "Subject: New message %d in mailbox %s\n", msgnum + 1, mailbox);
1754                 else
1755                         fprintf(p, "Subject: [PBX]: New message %d in mailbox %s\n", msgnum + 1, mailbox);
1756                 fprintf(p, "Message-ID: <Asterisk-%d-%d-%s-%d@%s>\n", msgnum, (unsigned int)rand(), mailbox, getpid(), host);
1757                 fprintf(p, "MIME-Version: 1.0\n");
1758                 if (attach_user_voicemail) {
1759                         /* Something unique. */
1760                         snprintf(bound, sizeof(bound), "voicemail_%d%s%d%d", msgnum, mailbox, getpid(), (unsigned int)rand());
1761
1762                         fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"\n\n\n", bound);
1763
1764                         fprintf(p, "--%s\n", bound);
1765                 }
1766                 fprintf(p, "Content-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n", charset);
1767                 if (emailbody) {
1768                         struct ast_channel *ast;
1769                         if ((ast = ast_channel_alloc(0))) {
1770                                 char *passdata;
1771                                 int vmlen = strlen(emailbody)*3 + 200;
1772                                 if ((passdata = alloca(vmlen))) {
1773                                         memset(passdata, 0, vmlen);
1774                                         prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
1775                                         pbx_substitute_variables_helper(ast,emailbody,passdata,vmlen);
1776                                         fprintf(p, "%s\n",passdata);
1777                                 } else ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1778                                 ast_channel_free(ast);
1779                         } else ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
1780                 } else {
1781                         fprintf(p, "Dear %s:\n\n\tJust wanted to let you know you were just left a %s long message (number %d)\n"
1782
1783                         "in mailbox %s from %s, on %s so you might\n"
1784                         "want to check it when you get a chance.  Thanks!\n\n\t\t\t\t--Asterisk\n\n", vmu->fullname, 
1785                         dur, msgnum + 1, mailbox, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
1786                 }
1787                 if (attach_user_voicemail) {
1788                         /* Eww. We want formats to tell us their own MIME type */
1789                         char *ctype = "audio/x-";
1790                         if (!strcasecmp(format, "ogg"))
1791                                 ctype = "application/";
1792                 
1793                         fprintf(p, "--%s\n", bound);
1794                         fprintf(p, "Content-Type: %s%s; name=\"msg%04d.%s\"\n", ctype, format, msgnum, format);
1795                         fprintf(p, "Content-Transfer-Encoding: base64\n");
1796                         fprintf(p, "Content-Description: Voicemail sound attachment.\n");
1797                         fprintf(p, "Content-Disposition: attachment; filename=\"msg%04d.%s\"\n\n", msgnum, format);
1798
1799                         snprintf(fname, sizeof(fname), "%s.%s", attach, format);
1800                         base_encode(fname, p);
1801                         fprintf(p, "\n\n--%s--\n.\n", bound);
1802                 }
1803                 fclose(p);
1804                 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
1805                 ast_safe_system(tmp2);
1806                 ast_log(LOG_DEBUG, "Sent mail to %s with command '%s'\n", vmu->email, mailcmd);
1807         }
1808         return 0;
1809 }
1810
1811 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)
1812 {
1813         char date[256];
1814         char host[MAXHOSTNAMELEN]="";
1815         char who[256];
1816         char dur[256];
1817         char tmp[80] = "/tmp/astmail-XXXXXX";
1818         char tmp2[256];
1819         struct tm tm;
1820         FILE *p = vm_mkftemp(tmp);
1821
1822         if (p == NULL) {
1823                 ast_log(LOG_WARNING, "Unable to launch '%s'\n", mailcmd);
1824                 return -1;
1825         } else {
1826                 gethostname(host, sizeof(host)-1);
1827                 if (strchr(srcemail, '@'))
1828                         ast_copy_string(who, srcemail, sizeof(who));
1829                 else {
1830                         snprintf(who, sizeof(who), "%s@%s", srcemail, host);
1831                 }
1832                 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
1833                 strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
1834                 fprintf(p, "Date: %s\n", date);
1835
1836                 if (*pagerfromstring) {
1837                         struct ast_channel *ast;
1838                         if ((ast = ast_channel_alloc(0))) {
1839                                 char *passdata;
1840                                 int vmlen = strlen(fromstring)*3 + 200;
1841                                 if ((passdata = alloca(vmlen))) {
1842                                         memset(passdata, 0, vmlen);
1843                                         prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
1844                                         pbx_substitute_variables_helper(ast,pagerfromstring,passdata,vmlen);
1845                                         fprintf(p, "From: %s <%s>\n",passdata,who);
1846                                 } else 
1847                                         ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1848                                 ast_channel_free(ast);
1849                         } else ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
1850                 } else
1851                         fprintf(p, "From: Asterisk PBX <%s>\n", who);
1852                 fprintf(p, "To: %s\n", pager);
1853                if (pagersubject) {
1854                        struct ast_channel *ast;
1855                        if ((ast = ast_channel_alloc(0))) {
1856                                char *passdata;
1857                                int vmlen = strlen(pagersubject)*3 + 200;
1858                                if ((passdata = alloca(vmlen))) {
1859                                        memset(passdata, 0, vmlen);
1860                                        prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
1861                                        pbx_substitute_variables_helper(ast,pagersubject,passdata,vmlen);
1862                                        fprintf(p, "Subject: %s\n\n",passdata);
1863                                } else ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1864                                ast_channel_free(ast);
1865                        } else ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
1866                } else
1867                        fprintf(p, "Subject: New VM\n\n");
1868                 strftime(date, sizeof(date), "%A, %B %d, %Y at %r", &tm);
1869                if (pagerbody) {
1870                        struct ast_channel *ast;
1871                        if ((ast = ast_channel_alloc(0))) {
1872                                char *passdata;
1873                                int vmlen = strlen(pagerbody)*3 + 200;
1874                                if ((passdata = alloca(vmlen))) {
1875                                        memset(passdata, 0, vmlen);
1876                                        prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
1877                                        pbx_substitute_variables_helper(ast,pagerbody,passdata,vmlen);
1878                                        fprintf(p, "%s\n",passdata);
1879                                } else ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1880                                ast_channel_free(ast);
1881                        } else ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
1882                } else {
1883                        fprintf(p, "New %s long msg in box %s\n"
1884                                        "from %s, on %s", dur, mailbox, (cidname ? cidname : (cidnum ? cidnum : "unknown")), date);
1885                }
1886                 fclose(p);
1887                 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
1888                 ast_safe_system(tmp2);
1889                 ast_log(LOG_DEBUG, "Sent page to %s with command '%s'\n", pager, mailcmd);
1890         }
1891         return 0;
1892 }
1893
1894 static int get_date(char *s, int len)
1895 {
1896         struct tm tm;
1897         time_t t;
1898         t = time(0);
1899         localtime_r(&t,&tm);
1900         return strftime(s, len, "%a %b %e %r %Z %Y", &tm);
1901 }
1902
1903 static int invent_message(struct ast_channel *chan, char *context, char *ext, int busy, char *ecodes)
1904 {
1905         int res;
1906         char fn[256];
1907         snprintf(fn, sizeof(fn), "%s%s/%s/greet", VM_SPOOL_DIR, context, ext);
1908         RETRIEVE(fn, -1);
1909         if (ast_fileexists(fn, NULL, NULL) > 0) {
1910                 res = ast_streamfile(chan, fn, chan->language);
1911                 if (res) {
1912                         DISPOSE(fn, -1);
1913                         return -1;
1914                 }
1915                 res = ast_waitstream(chan, ecodes);
1916                 if (res) {
1917                         DISPOSE(fn, -1);
1918                         return res;
1919                 }
1920         } else {
1921                 /* Dispose just in case */
1922                 DISPOSE(fn, -1);
1923                 res = ast_streamfile(chan, "vm-theperson", chan->language);
1924                 if (res)
1925                         return -1;
1926                 res = ast_waitstream(chan, ecodes);
1927                 if (res)
1928                         return res;
1929                 res = ast_say_digit_str(chan, ext, ecodes, chan->language);
1930                 if (res)
1931                         return res;
1932         }
1933         if (busy)
1934                 res = ast_streamfile(chan, "vm-isonphone", chan->language);
1935         else
1936                 res = ast_streamfile(chan, "vm-isunavail", chan->language);
1937         if (res)
1938                 return -1;
1939         res = ast_waitstream(chan, ecodes);
1940         return res;
1941 }
1942
1943 static void free_user(struct ast_vm_user *vmu)
1944 {
1945         if (ast_test_flag(vmu, VM_ALLOCED))
1946                 free(vmu);
1947 }
1948
1949 static void free_zone(struct vm_zone *z)
1950 {
1951         free(z);
1952 }
1953
1954 static char *mbox(int id)
1955 {
1956         switch(id) {
1957         case 0:
1958                 return "INBOX";
1959         case 1:
1960                 return "Old";
1961         case 2:
1962                 return "Work";
1963         case 3:
1964                 return "Family";
1965         case 4:
1966                 return "Friends";
1967         case 5:
1968                 return "Cust1";
1969         case 6:
1970                 return "Cust2";
1971         case 7:
1972                 return "Cust3";
1973         case 8:
1974                 return "Cust4";
1975         case 9:
1976                 return "Cust5";
1977         default:
1978                 return "Unknown";
1979         }
1980 }
1981
1982 #ifdef USE_ODBC_STORAGE
1983 static int messagecount(const char *mailbox, int *newmsgs, int *oldmsgs)
1984 {
1985         int x = 0;
1986         int res;
1987         SQLHSTMT stmt;
1988         char sql[256];
1989         char rowdata[20];
1990         char tmp[256]="";
1991         char *context;
1992
1993         if (newmsgs)
1994                 *newmsgs = 0;
1995         if (oldmsgs)
1996                 *oldmsgs = 0;
1997         /* If no mailbox, return immediately */
1998         if (ast_strlen_zero(mailbox))
1999                 return 0;
2000
2001         ast_copy_string(tmp, mailbox, sizeof(tmp));
2002         
2003         context = strchr(tmp, '@');
2004         if (context) {   
2005                 *context = '\0';
2006                 context++;
2007         } else  
2008                 context = "default";
2009         
2010         odbc_obj *obj;
2011         obj = fetch_odbc_obj(odbc_database, 0);
2012         if (obj) {
2013                 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
2014                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2015                         ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
2016                         goto yuck;
2017                 }
2018                 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir LIKE '%%%s/%s/%s'", odbc_table, context, tmp, "INBOX");
2019                 res = SQLPrepare(stmt, sql, SQL_NTS);
2020                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2021                         ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
2022                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2023                         goto yuck;
2024                 }
2025                 res = odbc_smart_execute(obj, stmt);
2026                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2027                         ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2028                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2029                         goto yuck;
2030                 }
2031                 res = SQLFetch(stmt);
2032                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2033                         ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2034                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2035                         goto yuck;
2036                 }
2037                 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2038                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2039                         ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2040                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2041                         goto yuck;
2042                 }
2043                 *newmsgs = atoi(rowdata);
2044                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2045
2046                 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
2047                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2048                         ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
2049                         goto yuck;
2050                 }
2051                 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir like '%%%s/%s/%s'", odbc_table, context, tmp, "Old");
2052                 res = SQLPrepare(stmt, sql, SQL_NTS);
2053                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2054                         ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
2055                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2056                         goto yuck;
2057                 }
2058                 res = odbc_smart_execute(obj, stmt);
2059                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2060                         ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2061                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2062                         goto yuck;
2063                 }
2064                 res = SQLFetch(stmt);
2065                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2066                         ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2067                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2068                         goto yuck;
2069                 }
2070                 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2071                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2072                         ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2073                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2074                         goto yuck;
2075                 }
2076                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2077                 *oldmsgs = atoi(rowdata);
2078                 x = 1;
2079         } else
2080                 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2081                 
2082 yuck:   
2083         return x;
2084 }
2085
2086 static int has_voicemail(const char *mailbox, const char *folder)
2087 {
2088         int nummsgs = 0;
2089         int res;
2090         SQLHSTMT stmt;
2091         char sql[256];
2092         char rowdata[20];
2093         char tmp[256]="";
2094         char *context;
2095         if (!folder)
2096                 folder = "INBOX";
2097         /* If no mailbox, return immediately */
2098         if (ast_strlen_zero(mailbox))
2099                 return 0;
2100
2101         ast_copy_string(tmp, mailbox, sizeof(tmp));
2102                         
2103         context = strchr(tmp, '@');
2104         if (context) {
2105                 *context = '\0';
2106                 context++;
2107         } else
2108                 context = "default";
2109
2110         odbc_obj *obj;
2111         obj = fetch_odbc_obj(odbc_database, 0);
2112         if (obj) {
2113                 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
2114                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2115                         ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
2116                         goto yuck;
2117                 }
2118                 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir like '%%%s/%s/%s'", odbc_table, context, tmp, "INBOX");
2119                 res = SQLPrepare(stmt, sql, SQL_NTS);
2120                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {  
2121                         ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
2122                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2123                         goto yuck;
2124                 }
2125                 res = odbc_smart_execute(obj, stmt);
2126                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2127                         ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2128                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2129                         goto yuck;
2130                 }
2131                 res = SQLFetch(stmt);
2132                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2133                         ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2134                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2135                         goto yuck;
2136                 }
2137                 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2138                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2139                         ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2140                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2141                         goto yuck;
2142                 }
2143                 nummsgs = atoi(rowdata);
2144                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2145        } else
2146                 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2147
2148 yuck:
2149         if (nummsgs>=1)
2150                 return 1;
2151         else
2152                 return 0;
2153 }
2154
2155 #else
2156
2157 static int has_voicemail(const char *mailbox, const char *folder)
2158 {
2159         DIR *dir;
2160         struct dirent *de;
2161         char fn[256];
2162         char tmp[256]="";
2163         char *mb, *cur;
2164         char *context;
2165         int ret;
2166         if (!folder)
2167                 folder = "INBOX";
2168         /* If no mailbox, return immediately */
2169         if (ast_strlen_zero(mailbox))
2170                 return 0;
2171         if (strchr(mailbox, ',')) {
2172                 ast_copy_string(tmp, mailbox, sizeof(tmp));
2173                 mb = tmp;
2174                 ret = 0;
2175                 while((cur = strsep(&mb, ","))) {
2176                         if (!ast_strlen_zero(cur)) {
2177                                 if (has_voicemail(cur, folder))
2178                                         return 1; 
2179                         }
2180                 }
2181                 return 0;
2182         }
2183         ast_copy_string(tmp, mailbox, sizeof(tmp));
2184         context = strchr(tmp, '@');
2185         if (context) {
2186                 *context = '\0';
2187                 context++;
2188         } else
2189                 context = "default";
2190         snprintf(fn, sizeof(fn), "%s/%s/%s/%s", VM_SPOOL_DIR, context, tmp, folder);
2191         dir = opendir(fn);
2192         if (!dir)
2193                 return 0;
2194         while ((de = readdir(dir))) {
2195                 if (!strncasecmp(de->d_name, "msg", 3))
2196                         break;
2197         }
2198         closedir(dir);
2199         if (de)
2200                 return 1;
2201         return 0;
2202 }
2203
2204
2205 static int messagecount(const char *mailbox, int *newmsgs, int *oldmsgs)
2206 {
2207         DIR *dir;
2208         struct dirent *de;
2209         char fn[256];
2210         char tmp[256];
2211         char *context;
2212
2213         if (newmsgs)
2214                 *newmsgs = 0;
2215         if (oldmsgs)
2216                 *oldmsgs = 0;
2217         /* If no mailbox, return immediately */
2218         if (ast_strlen_zero(mailbox))
2219                 return 0;
2220         if (strchr(mailbox, ',')) {
2221                 int tmpnew, tmpold;
2222                 char *mb, *cur;
2223
2224                 ast_copy_string(tmp, mailbox, sizeof(tmp));
2225                 mb = tmp;
2226                 while((cur = strsep(&mb, ", "))) {
2227                         if (!ast_strlen_zero(cur)) {
2228                                 if (messagecount(cur, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2229                                         return -1;
2230                                 else {
2231                                         if (newmsgs)
2232                                                 *newmsgs += tmpnew; 
2233                                         if (oldmsgs)
2234                                                 *oldmsgs += tmpold;
2235                                 }
2236                         }
2237                 }
2238                 return 0;
2239         }
2240         ast_copy_string(tmp, mailbox, sizeof(tmp));
2241         context = strchr(tmp, '@');
2242         if (context) {
2243                 *context = '\0';
2244                 context++;
2245         } else
2246                 context = "default";
2247         if (newmsgs) {
2248                 snprintf(fn, sizeof(fn), "%s/%s/%s/INBOX", VM_SPOOL_DIR, context, tmp);
2249                 dir = opendir(fn);
2250                 if (dir) {
2251                         while ((de = readdir(dir))) {
2252                                 if ((strlen(de->d_name) > 3) && !strncasecmp(de->d_name, "msg", 3) &&
2253                                         !strcasecmp(de->d_name + strlen(de->d_name) - 3, "txt"))
2254                                                 (*newmsgs)++;
2255                                         
2256                         }
2257                         closedir(dir);
2258                 }
2259         }
2260         if (oldmsgs) {
2261                 snprintf(fn, sizeof(fn), "%s/%s/%s/Old", VM_SPOOL_DIR, context, tmp);
2262                 dir = opendir(fn);
2263                 if (dir) {
2264                         while ((de = readdir(dir))) {
2265                                 if ((strlen(de->d_name) > 3) && !strncasecmp(de->d_name, "msg", 3) &&
2266                                         !strcasecmp(de->d_name + strlen(de->d_name) - 3, "txt"))
2267                                                 (*oldmsgs)++;
2268                                         
2269                         }
2270                         closedir(dir);
2271                 }
2272         }
2273         return 0;
2274 }
2275
2276 #endif
2277
2278 static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu, int msgnum, long duration, char *fmt, char *cidnum, char *cidname);
2279
2280 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)
2281 {
2282         char fromdir[256], todir[256], frompath[256], topath[256];
2283         char *frombox = mbox(imbox);
2284         int recipmsgnum;
2285
2286         ast_log(LOG_NOTICE, "Copying message from %s@%s to %s@%s\n", vmu->mailbox, vmu->context, recip->mailbox, recip->context);
2287
2288         create_dirpath(todir, sizeof(todir), recip->context, recip->mailbox, "INBOX");
2289   
2290         make_dir(fromdir, sizeof(fromdir), vmu->context, vmu->mailbox, frombox);
2291         make_file(frompath, sizeof(frompath), fromdir, msgnum);
2292
2293         if (vm_lock_path(todir))
2294                 return ERROR_LOCK_PATH;
2295
2296         recipmsgnum = 0;
2297         do {
2298                 make_file(topath, sizeof(topath), todir, recipmsgnum);
2299                 if (!EXISTS(todir, recipmsgnum, topath, chan->language))
2300                         break;
2301                 recipmsgnum++;
2302         } while (recipmsgnum < recip->maxmsg);
2303         if (recipmsgnum < recip->maxmsg) {
2304                 COPY(fromdir, msgnum, todir, recipmsgnum, recip->mailbox, recip->context, frompath, topath);
2305         } else {
2306                 ast_log(LOG_ERROR, "Recipient mailbox %s@%s is full\n", recip->mailbox, recip->context);
2307         }
2308         ast_unlock_path(todir);
2309         notify_new_message(chan, recip, recipmsgnum, duration, fmt, chan->cid.cid_num, chan->cid.cid_name);
2310         
2311         return 0;
2312 }
2313
2314 static void run_externnotify(char *context, char *extension)
2315 {
2316         char arguments[255];
2317         char ext_context[256] = "";
2318         int newvoicemails = 0, oldvoicemails = 0;
2319 #ifdef WITH_SMDI
2320         struct ast_smdi_mwi_message *mwi_msg;
2321 #endif
2322
2323         if (!ast_strlen_zero(context))
2324                 snprintf(ext_context, sizeof(ext_context), "%s@%s", extension, context);
2325         else
2326                 ast_copy_string(ext_context, extension, sizeof(ext_context));
2327
2328 #ifdef WITH_SMDI
2329         if (!strcasecmp(externnotify, "smdi")) {
2330                 if (ast_app_has_voicemail(ext_context, NULL)) 
2331                         ast_smdi_mwi_set(smdi_iface, extension);
2332                 else
2333                         ast_smdi_mwi_unset(smdi_iface, extension);
2334
2335                 if ((mwi_msg = ast_smdi_mwi_message_wait(smdi_iface, SMDI_MWI_WAIT_TIMEOUT))) {
2336                         ast_log(LOG_ERROR, "Error executing SMDI MWI change for %s on %s\n", extension, smdi_iface->name);
2337                         if (!strncmp(mwi_msg->cause, "INV", 3))
2338                                 ast_log(LOG_ERROR, "Invalid MWI extension: %s\n", mwi_msg->fwd_st);
2339                         else if (!strncmp(mwi_msg->cause, "BLK", 3))
2340                                 ast_log(LOG_WARNING, "MWI light was already on or off for %s\n", mwi_msg->fwd_st);
2341                         ast_log(LOG_WARNING, "The switch reported '%s'\n", mwi_msg->cause);
2342                         ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
2343                 } else {
2344                         ast_log(LOG_DEBUG, "Successfully executed SMDI MWI change for %s on %s\n", extension, smdi_iface->name);
2345                 }
2346         } else if (!ast_strlen_zero(externnotify)) {
2347 #else
2348         if (!ast_strlen_zero(externnotify)) {
2349 #endif
2350                 if (messagecount(ext_context, &newvoicemails, &oldvoicemails)) {
2351                         ast_log(LOG_ERROR, "Problem in calculating number of voicemail messages available for extension %s\n", extension);
2352                 } else {
2353                         snprintf(arguments, sizeof(arguments), "%s %s %s %d&", externnotify, context, extension, newvoicemails);
2354                         ast_log(LOG_DEBUG, "Executing %s\n", arguments);
2355                         ast_safe_system(arguments);
2356                 }
2357         }
2358 }
2359
2360 struct leave_vm_options {
2361         unsigned int flags;
2362         signed char record_gain;
2363 };
2364
2365 static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_options *options)
2366 {
2367         char tmptxtfile[256], txtfile[256];
2368         char callerid[256];
2369         FILE *txt;
2370         int res = 0;
2371         int msgnum;
2372         int duration = 0;
2373         int ausemacro = 0;
2374         int ousemacro = 0;
2375         char date[256];
2376         char dir[256];
2377         char fn[256];
2378         char prefile[256]="";
2379         char tempfile[256]="";
2380         char ext_context[256] = "";
2381         char fmt[80];
2382         char *context;
2383         char ecodes[16] = "#";
2384         char tmp[256] = "", *tmpptr;
2385         struct ast_vm_user *vmu;
2386         struct ast_vm_user svm;
2387         const char *category = NULL;
2388
2389         ast_copy_string(tmp, ext, sizeof(tmp));
2390         ext = tmp;
2391         context = strchr(tmp, '@');
2392         if (context) {
2393                 *context = '\0';
2394                 context++;
2395                 tmpptr = strchr(context, '&');
2396         } else {
2397                 tmpptr = strchr(ext, '&');
2398         }
2399
2400         if (tmpptr) {
2401                 *tmpptr = '\0';
2402                 tmpptr++;
2403         }
2404
2405         category = pbx_builtin_getvar_helper(chan, "VM_CATEGORY");
2406
2407         if (!(vmu = find_user(&svm, context, ext))) {
2408                 ast_log(LOG_WARNING, "No entry in voicemail config file for '%s'\n", ext);
2409                 if (ast_test_flag(options, OPT_PRIORITY_JUMP) || ast_opt_priority_jumping)
2410                         ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
2411                 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
2412                 return res;
2413         }
2414
2415         /* Setup pre-file if appropriate */
2416         if (strcmp(vmu->context, "default"))
2417                 snprintf(ext_context, sizeof(ext_context), "%s@%s", ext, vmu->context);
2418         else
2419                 ast_copy_string(ext_context, vmu->context, sizeof(ext_context));
2420         if (ast_test_flag(options, OPT_BUSY_GREETING))
2421                 snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, ext);
2422         else if (ast_test_flag(options, OPT_UNAVAIL_GREETING))
2423                 snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, ext);
2424         snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", VM_SPOOL_DIR, vmu->context, ext);
2425         RETRIEVE(tempfile, -1);
2426         if (ast_fileexists(tempfile, NULL, NULL) > 0)
2427                 ast_copy_string(prefile, tempfile, sizeof(prefile));
2428         DISPOSE(tempfile, -1);
2429         /* It's easier just to try to make it than to check for its existence */
2430         create_dirpath(dir, sizeof(dir), vmu->context, ext, "INBOX");
2431
2432         /* Check current or macro-calling context for special extensions */
2433         if (!ast_strlen_zero(vmu->exit)) {
2434                 if (ast_exists_extension(chan, vmu->exit, "o", 1, chan->cid.cid_num))
2435                         strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2436         } else if (ast_exists_extension(chan, chan->context, "o", 1, chan->cid.cid_num))
2437                 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2438         else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num)) {
2439                 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2440                 ousemacro = 1;
2441         }
2442
2443         if (!ast_strlen_zero(vmu->exit)) {
2444                 if (ast_exists_extension(chan, vmu->exit, "a", 1, chan->cid.cid_num))
2445                         strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
2446         } else if (ast_exists_extension(chan, chan->context, "a", 1, chan->cid.cid_num))
2447                 strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
2448         else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "a", 1, chan->cid.cid_num)) {
2449                 strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
2450                 ausemacro = 1;
2451         }
2452
2453         /* Play the beginning intro if desired */
2454         if (!ast_strlen_zero(prefile)) {
2455                 RETRIEVE(prefile, -1);
2456                 if (ast_fileexists(prefile, NULL, NULL) > 0) {
2457                         if (ast_streamfile(chan, prefile, chan->language) > -1) 
2458                                 res = ast_waitstream(chan, ecodes);
2459                 } else {
2460                         ast_log(LOG_DEBUG, "%s doesn't exist, doing what we can\n", prefile);
2461                         res = invent_message(chan, vmu->context, ext, ast_test_flag(options, OPT_BUSY_GREETING), ecodes);
2462                 }
2463                 DISPOSE(prefile, -1);
2464                 if (res < 0) {
2465                         ast_log(LOG_DEBUG, "Hang up during prefile playback\n");
2466                         free_user(vmu);
2467                         pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
2468                         return -1;
2469                 }
2470         }
2471         if (res == '#') {
2472                 /* On a '#' we skip the instructions */
2473                 ast_set_flag(options, OPT_SILENT);
2474                 res = 0;
2475         }
2476         if (!res && !ast_test_flag(options, OPT_SILENT)) {
2477                 res = ast_streamfile(chan, INTRO, chan->language);
2478                 if (!res)
2479                         res = ast_waitstream(chan, ecodes);
2480                 if (res == '#') {
2481                         ast_set_flag(options, OPT_SILENT);
2482                         res = 0;
2483                 }
2484         }
2485         if (res > 0)
2486                 ast_stopstream(chan);
2487         /* Check for a '*' here in case the caller wants to escape from voicemail to something
2488            other than the operator -- an automated attendant or mailbox login for example */
2489         if (res == '*') {
2490                 chan->exten[0] = 'a';
2491                 chan->exten[1] = '\0';
2492                 if (!ast_strlen_zero(vmu->exit)) {
2493                         ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
2494                 } else if (ausemacro && !ast_strlen_zero(chan->macrocontext)) {
2495                         ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
2496                 }
2497                 chan->priority = 0;
2498                 free_user(vmu);
2499                 pbx_builtin_setvar_helper(chan, "VMSTATUS", "USEREXIT");
2500                 return 0;
2501         }
2502         /* Check for a '0' here */
2503         if (res == '0') {
2504         transfer:
2505                 if (ast_test_flag(vmu, VM_OPERATOR)) {
2506                         chan->exten[0] = 'o';
2507                         chan->exten[1] = '\0';
2508                         if (!ast_strlen_zero(vmu->exit)) {
2509                                 ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
2510                         } else if (ousemacro && !ast_strlen_zero(chan->macrocontext)) {
2511                                 ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
2512                         }
2513                         ast_play_and_wait(chan, "transfer");
2514                         chan->priority = 0;
2515                         free_user(vmu);
2516                         pbx_builtin_setvar_helper(chan, "VMSTATUS", "USEREXIT");
2517                         return 0;
2518                 } else {
2519                         ast_play_and_wait(chan, "vm-sorry");
2520                         pbx_builtin_setvar_helper(chan, "VMSTATUS", "USEREXIT");
2521                         return 0;
2522                 }
2523         }
2524         if (res < 0) {
2525                 free_user(vmu);
2526                 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
2527                 return -1;
2528         }
2529         /* The meat of recording the message...  All the announcements and beeps have been played*/
2530         ast_copy_string(fmt, vmfmts, sizeof(fmt));
2531         if (!ast_strlen_zero(fmt)) {
2532                 msgnum = 0;
2533
2534                 if (vm_lock_path(dir)) {
2535                         free_user(vmu);
2536                         return ERROR_LOCK_PATH;
2537                 }
2538
2539                 /* 
2540                  * This operation can be very expensive if done say over NFS or if the mailbox has 100+ messages
2541                  * in the folder.  So we should get this first so we don't cut off the first few seconds of the 
2542                  * message.  
2543                  */
2544                 do {
2545                         make_file(fn, sizeof(fn), dir, msgnum);
2546                         if (!EXISTS(dir,msgnum,fn,chan->language))
2547                                 break;
2548                         msgnum++;
2549                 } while (msgnum < vmu->maxmsg);
2550
2551                 /* Now play the beep once we have the message number for our next message. */
2552                 if (res >= 0) {
2553                         /* Unless we're *really* silent, try to send the beep */
2554                         res = ast_streamfile(chan, "beep", chan->language);
2555                         if (!res)
2556                                 res = ast_waitstream(chan, "");
2557                 }
2558                 if (msgnum < vmu->maxmsg) {
2559                         /* assign a variable with the name of the voicemail file */       
2560                         pbx_builtin_setvar_helper(chan, "VM_MESSAGEFILE", fn);
2561                                 
2562                         /* Store information */
2563                         snprintf(txtfile, sizeof(txtfile), "%s.txt", fn);
2564                         snprintf(tmptxtfile, sizeof(tmptxtfile), "%s.txt.tmp", fn);
2565                         txt = fopen(tmptxtfile, "w+");
2566                         if (txt) {
2567                                 get_date(date, sizeof(date));
2568                                 fprintf(txt, 
2569                                         ";\n"
2570                                         "; Message Information file\n"
2571                                         ";\n"
2572                                         "[message]\n"
2573                                         "origmailbox=%s\n"
2574                                         "context=%s\n"
2575                                         "macrocontext=%s\n"
2576                                         "exten=%s\n"
2577                                         "priority=%d\n"
2578                                         "callerchan=%s\n"
2579                                         "callerid=%s\n"
2580                                         "origdate=%s\n"
2581                                         "origtime=%ld\n"
2582                                         "category=%s\n",
2583                                         ext,
2584                                         chan->context,
2585                                         chan->macrocontext, 
2586                                         chan->exten,
2587                                         chan->priority,
2588                                         chan->name,
2589                                         ast_callerid_merge(callerid, sizeof(callerid), chan->cid.cid_name, chan->cid.cid_num, "Unknown"),
2590                                         date, (long)time(NULL),
2591                                         category ? category : ""); 
2592                         } else
2593                                 ast_log(LOG_WARNING, "Error opening text file for output\n");
2594                         res = play_record_review(chan, NULL, fn, vmmaxmessage, fmt, 1, vmu, &duration, dir, options->record_gain);
2595                         if (res == '0') {
2596                                 if (txt)
2597                                         fclose(txt);
2598                                 goto transfer;
2599                         }
2600                         if (res > 0)
2601                                 res = 0;
2602                         if (txt) {
2603                                 fprintf(txt, "duration=%d\n", duration);
2604                                 fclose(txt);
2605                                 rename(tmptxtfile, txtfile);
2606                         }
2607                                 
2608                         if (duration < vmminmessage) {
2609                                 if (option_verbose > 2) 
2610                                         ast_verbose( VERBOSE_PREFIX_3 "Recording was %d seconds long but needs to be at least %d - abandoning\n", duration, vmminmessage);
2611                                 DELETE(dir,msgnum,fn);
2612                                 /* XXX We should really give a prompt too short/option start again, with leave_vm_out called only after a timeout XXX */
2613                                 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
2614                                 goto leave_vm_out;
2615                         }
2616                         /* Are there to be more recipients of this message? */
2617                         while (tmpptr) {
2618                                 struct ast_vm_user recipu, *recip;
2619                                 char *exten, *context;
2620                                         
2621                                 exten = strsep(&tmpptr, "&");
2622                                 context = strchr(exten, '@');
2623                                 if (context) {
2624                                         *context = '\0';
2625                                         context++;
2626                                 }
2627                                 if ((recip = find_user(&recipu, context, exten))) {
2628                                         copy_message(chan, vmu, 0, msgnum, duration, recip, fmt);
2629                                         free_user(recip);
2630                                 }
2631                         }
2632                         if (ast_fileexists(fn, NULL, NULL)) {
2633                                 STORE(dir, vmu->mailbox, vmu->context, msgnum);
2634                                 notify_new_message(chan, vmu, msgnum, duration, fmt, chan->cid.cid_num, chan->cid.cid_name);
2635                                 DISPOSE(dir, msgnum);
2636                         }
2637                         pbx_builtin_setvar_helper(chan, "VMSTATUS", "SUCCESS");
2638                 } else {
2639                         ast_unlock_path(dir);
2640                         res = ast_streamfile(chan, "vm-mailboxfull", chan->language);
2641                         if (!res)
2642                                 res = ast_waitstream(chan, "");
2643                         ast_log(LOG_WARNING, "No more messages possible\n");
2644                         pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
2645                 }
2646         } else
2647                 ast_log(LOG_WARNING, "No format for saving voicemail?\n");
2648  leave_vm_out:
2649         free_user(vmu);
2650         
2651         return res;
2652 }
2653
2654 static int resequence_mailbox(struct ast_vm_user *vmu, char *dir)
2655 {
2656         /* we know max messages, so stop process when number is hit */
2657
2658         int x,dest;
2659         char sfn[256];
2660         char dfn[256];
2661
2662         if (vm_lock_path(dir))
2663                 return ERROR_LOCK_PATH;
2664
2665         for (x = 0, dest = 0; x < vmu->maxmsg; x++) {
2666                 make_file(sfn, sizeof(sfn), dir, x);
2667                 if (EXISTS(dir, x, sfn, NULL)) {
2668                         
2669                         if(x != dest) {
2670                                 make_file(dfn, sizeof(dfn), dir, dest);
2671                                 RENAME(dir, x, vmu->mailbox, vmu->context, dir, dest, sfn, dfn);
2672                         }
2673                         
2674                         dest++;
2675                 }
2676         }
2677         ast_unlock_path(dir);
2678
2679         return 0;
2680 }
2681
2682
2683 static int say_and_wait(struct ast_channel *chan, int num, const char *language)
2684 {
2685         int d;
2686         d = ast_say_number(chan, num, AST_DIGIT_ANY, language, (char *) NULL);
2687         return d;
2688 }
2689
2690 static int save_to_folder(struct ast_vm_user *vmu, char *dir, int msg, char *context, char *username, int box)
2691 {
2692         char sfn[256];
2693         char dfn[256];
2694         char ddir[256];
2695         char *dbox = mbox(box);
2696         int x;
2697         make_file(sfn, sizeof(sfn), dir, msg);
2698         create_dirpath(ddir, sizeof(ddir), context, username, dbox);
2699
2700         if (vm_lock_path(ddir))
2701                 return ERROR_LOCK_PATH;
2702
2703         for (x = 0; x < vmu->maxmsg; x++) {
2704                 make_file(dfn, sizeof(dfn), ddir, x);
2705                 if (!EXISTS(ddir, x, dfn, NULL))
2706                         break;
2707         }
2708         if (x >= vmu->maxmsg) {
2709                 ast_unlock_path(ddir);
2710                 return -1;
2711         }
2712         if (strcmp(sfn, dfn)) {
2713                 COPY(dir, msg, ddir, x, username, context, sfn, dfn);
2714         }
2715         ast_unlock_path(ddir);
2716         
2717         return 0;
2718 }
2719
2720 static int adsi_logo(unsigned char *buf)
2721 {
2722         int bytes = 0;
2723         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 1, ADSI_JUST_CENT, 0, "Comedian Mail", "");
2724         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 2, ADSI_JUST_CENT, 0, "(C)2002 LSS, Inc.", "");
2725         return bytes;
2726 }
2727
2728 static int adsi_load_vmail(struct ast_channel *chan, int *useadsi)
2729 {
2730         unsigned char buf[256];
2731         int bytes=0;
2732         int x;
2733         char num[5];
2734
2735         *useadsi = 0;
2736         bytes += adsi_data_mode(buf + bytes);
2737         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
2738
2739         bytes = 0;
2740         bytes += adsi_logo(buf);
2741         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, "Downloading Scripts", "");
2742 #ifdef DISPLAY
2743         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_LEFT, 0, "   .", "");
2744 #endif
2745         bytes += adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
2746         bytes += adsi_data_mode(buf + bytes);
2747         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
2748
2749         if (adsi_begin_download(chan, addesc, adsifdn, adsisec, adsiver)) {
2750                 bytes = 0;
2751                 bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, "Load Cancelled.", "");
2752                 bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_CENT, 0, "ADSI Unavailable", "");
2753                 bytes += adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
2754                 bytes += adsi_voice_mode(buf + bytes, 0);
2755                 adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
2756                 return 0;
2757         }
2758
2759 #ifdef DISPLAY
2760         /* Add a dot */
2761         bytes = 0;
2762         bytes += adsi_logo(buf);
2763         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, "Downloading Scripts", "");
2764         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_LEFT, 0, "   ..", "");
2765         bytes += adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
2766         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
2767 #endif
2768         bytes = 0;
2769         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 0, "Listen", "Listen", "1", 1);
2770         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 1, "Folder", "Folder", "2", 1);
2771         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 2, "Advanced", "Advnced", "3", 1);
2772         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 3, "Options", "Options", "0", 1);
2773         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 4, "Help", "Help", "*", 1);
2774         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 5, "Exit", "Exit", "#", 1);
2775         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD);
2776
2777 #ifdef DISPLAY
2778         /* Add another dot */
2779         bytes = 0;
2780         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_LEFT, 0, "   ...", "");
2781         bytes += adsi_voice_mode(buf + bytes, 0);
2782
2783         bytes += adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
2784         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
2785 #endif
2786
2787         bytes = 0;
2788         /* These buttons we load but don't use yet */
2789         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 6, "Previous", "Prev", "4", 1);
2790         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 8, "Repeat", "Repeat", "5", 1);
2791         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 7, "Delete", "Delete", "7", 1);
2792         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 9, "Next", "Next", "6", 1);
2793         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 10, "Save", "Save", "9", 1);
2794         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 11, "Undelete", "Restore", "7", 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         bytes = 0;
2806         for (x=0;x<5;x++) {
2807                 snprintf(num, sizeof(num), "%d", x);
2808                 bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 12 + x, mbox(x), mbox(x), num, 1);
2809         }
2810         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 12 + 5, "Cancel", "Cancel", "#", 1);
2811         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD);
2812
2813 #ifdef DISPLAY
2814         /* Add another dot */
2815         bytes = 0;
2816         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_LEFT, 0, "   .....", "");
2817         bytes += adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
2818         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
2819 #endif
2820
2821         if (adsi_end_download(chan)) {
2822                 bytes = 0;
2823                 bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, "Download Unsuccessful.", "");
2824                 bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_CENT, 0, "ADSI Unavailable", "");
2825                 bytes += adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
2826                 bytes += adsi_voice_mode(buf + bytes, 0);
2827                 adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
2828                 return 0;
2829         }
2830         bytes = 0;
2831         bytes += adsi_download_disconnect(buf + bytes);
2832         bytes += adsi_voice_mode(buf + bytes, 0);
2833         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD);
2834
2835         ast_log(LOG_DEBUG, "Done downloading scripts...\n");
2836
2837 #ifdef DISPLAY
2838         /* Add last dot */
2839         bytes = 0;
2840         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_CENT, 0, "   ......", "");
2841         bytes += adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
2842 #endif
2843         ast_log(LOG_DEBUG, "Restarting session...\n");
2844
2845         bytes = 0;
2846         /* Load the session now */
2847         if (adsi_load_session(chan, adsifdn, adsiver, 1) == 1) {
2848                 *useadsi = 1;
2849                 bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, "Scripts Loaded!", "");
2850         } else
2851                 bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, "Load Failed!", "");
2852
2853         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
2854         return 0;
2855 }
2856
2857 static void adsi_begin(struct ast_channel *chan, int *useadsi)
2858 {
2859         int x;
2860         if (!adsi_available(chan))
2861                 return;
2862         x = adsi_load_session(chan, adsifdn, adsiver, 1);
2863         if (x < 0)
2864                 return;
2865         if (!x) {
2866                 if (adsi_load_vmail(chan, useadsi)) {
2867                         ast_log(LOG_WARNING, "Unable to upload voicemail scripts\n");
2868                         return;
2869                 }
2870         } else
2871                 *useadsi = 1;
2872 }
2873
2874 static void adsi_login(struct ast_channel *chan)
2875 {
2876         unsigned char buf[256];
2877         int bytes=0;
2878         unsigned char keys[8];
2879         int x;
2880         if (!adsi_available(chan))
2881                 return;
2882
2883         for (x=0;x<8;x++)
2884                 keys[x] = 0;
2885         /* Set one key for next */
2886         keys[3] = ADSI_KEY_APPS + 3;
2887
2888         bytes += adsi_logo(buf + bytes);
2889         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, " ", "");
2890         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_CENT, 0, " ", "");
2891         bytes += adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
2892         bytes += adsi_input_format(buf + bytes, 1, ADSI_DIR_FROM_LEFT, 0, "Mailbox: ******", "");
2893         bytes += adsi_input_control(buf + bytes, ADSI_COMM_PAGE, 4, 1, 1, ADSI_JUST_LEFT);
2894         bytes += adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 3, "Enter", "Enter", "#", 1);
2895         bytes += adsi_set_keys(buf + bytes, keys);
2896         bytes += adsi_voice_mode(buf + bytes, 0);
2897         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
2898 }
2899
2900 static void adsi_password(struct ast_channel *chan)
2901 {
2902         unsigned char buf[256];
2903         int bytes=0;
2904         unsigned char keys[8];
2905         int x;
2906         if (!adsi_available(chan))
2907                 return;
2908
2909         for (x=0;x<8;x++)
2910                 keys[x] = 0;
2911         /* Set one key for next */
2912         keys[3] = ADSI_KEY_APPS + 3;
2913
2914         bytes += adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
2915         bytes += adsi_input_format(buf + bytes, 1, ADSI_DIR_FROM_LEFT, 0, "Password: ******", "");
2916         bytes += adsi_input_control(buf + bytes, ADSI_COMM_PAGE, 4, 0, 1, ADSI_JUST_LEFT);
2917         bytes += adsi_set_keys(buf + bytes, keys);
2918         bytes += adsi_voice_mode(buf + bytes, 0);
2919         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
2920 }
2921
2922 static void adsi_folders(struct ast_channel *chan, int start, char *label)
2923 {
2924         unsigned char buf[256];
2925         int bytes=0;
2926         unsigned char keys[8];
2927         int x,y;
2928
2929         if (!adsi_available(chan))
2930                 return;
2931
2932         for (x=0;x<5;x++) {
2933                 y = ADSI_KEY_APPS + 12 + start + x;
2934                 if (y > ADSI_KEY_APPS + 12 + 4)
2935                         y = 0;
2936                 keys[x] = ADSI_KEY_SKT | y;
2937         }
2938         keys[5] = ADSI_KEY_SKT | (ADSI_KEY_APPS + 17);
2939         keys[6] = 0;
2940         keys[7] = 0;
2941
2942         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 1, ADSI_JUST_CENT, 0, label, "");
2943         bytes += adsi_display(buf + bytes, ADSI_COMM_PAGE, 2, ADSI_JUST_CENT, 0, " ", "");
2944         bytes += adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
2945         bytes += adsi_set_keys(buf + bytes, keys);
2946         bytes += adsi_voice_mode(buf + bytes, 0);
2947
2948         adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
2949 }
2950
2951 static void adsi_message(struct ast_channel *chan, struct vm_state *vms)
2952 {
2953         int bytes=0;
2954         unsigned char buf[256]; 
2955         char buf1[256], buf2[256];
2956         char fn2[256];
2957
2958         char cid[256]="";
2959         char *val;
2960         char *name, *num;
2961         char datetime[21]="";
2962         FILE *f;
2963
2964         unsigned char keys[8];
2965
2966         int x;
2967
2968         if (!adsi_available(chan))
2969                 return;
2970
2971         /* Retrieve important info */
2972         snprintf(fn2, sizeof(fn2), "%s.txt", vms->fn);
2973         f = fopen(fn2, "r");
2974         if (f) {
2975                 while (!feof(f)) {      
2976                         fgets((char *)buf, sizeof(buf), f);
2977                         if (!feof(f)) {
2978                                 char *stringp=NULL;
2979                                 stringp = (char *)buf;
2980                                 strsep(&stringp, "=");
2981                                 val = strsep(&stringp, "=");
2982                                 if (!ast_strlen_zero(val)) {
2983                                         if (!strcmp((char *)buf, "callerid"))
2984                                                 ast_copy_string(cid, val, sizeof(cid));
2985                                         if (!strcmp((char *)buf, "origdate"))
2986                                                 ast_copy_string(datetime, val, sizeof(datetime));
2987                                 }
2988                         }
2989                 }
2990                 fclose(f);
2991         }
2992         /* New meaning for keys */
2993         for (x=0;x<5;x++)
2994                 keys[x] = ADSI_KEY_SKT | (ADSI_KEY_APPS + 6 + x);
2995         keys[6] = 0x0;
2996         keys[7] = 0x0;
2997
2998         if (!vms->curmsg) {
2999                 /* No prev key, provide "Folder" instead */
3000                 keys[0] = ADSI_KEY_SKT | (ADSI_KEY_APPS + 1);
3001         }
3002         if (vms->curmsg >= vms->lastmsg) {
3003                 /* If last message ... */
3004                 if (vms->curmsg) {
3005                         /* but not only message, provide "Folder" instead */
3006                         keys[3] = ADSI_KEY_SKT | (ADSI_KEY_APPS + 1);
3007                         bytes += adsi_voice_mode(buf + bytes, 0);
3008
3009                 } else {
3010                         /* Otherwise if only message, leave blank */
3011                         keys[3] = 1;
3012                 }
3013         }
3014
3015         if (!ast_strlen_zero(cid)) {
3016                 ast_callerid_parse(cid, &name, &num);
3017                 if (!name)
3018                         name = num;
3019         } else
3020                 name = "Unknown Caller";
3021
3022         /* If deleted, show "undeleted" */
3023
3024         if (vms->deleted[vms->curmsg])
3025                 keys[1] = ADSI_KEY_SKT | (ADSI_KEY_APPS + 11);
3026
3027         /* Except "Exit" */
3028         keys[5] = ADSI_KEY_SKT | (ADSI_KEY_APPS + 5);
3029 &nb