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