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