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