2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2006, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
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.
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.
21 * \brief Comedian Mail - Voicemail System
23 * \author Mark Spencer <markster@digium.com>
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/
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
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.
44 <depend>res_smdi</depend>
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>
52 <conflict>IMAP_STORAGE</conflict>
53 <defaultenabled>no</defaultenabled>
55 <member name="IMAP_STORAGE" displayname="Storage of Voicemail using IMAP4">
56 <depend>imap_tk</depend>
57 <conflict>ODBC_STORAGE</conflict>
59 <defaultenabled>no</defaultenabled>
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
74 #ifdef USE_SYSTEM_IMAP
75 #include <imap/c-client.h>
76 #include <imap/imap4r1.h>
77 #include <imap/linkage.h>
87 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
89 #include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */
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"
115 #include "asterisk/res_odbc.h"
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];
127 static int expungeonhangup = 1;
128 static int imapgreetings = 0;
129 static char delimiter = '\0';
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);
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);
162 struct vm_state *vms;
163 AST_LIST_ENTRY(vmstate) list;
166 static AST_LIST_HEAD_STATIC(vmstates, vmstate);
170 #define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
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
178 #define VOICEMAIL_CONFIG "voicemail.conf"
179 #define ASTERISK_USERNAME "asterisk"
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? */
191 /* Default mail command to mail voicemail. Change it with the
192 mailcmd= command in voicemail.conf */
193 #define SENDMAIL "/usr/sbin/sendmail -t"
195 #define INTRO "vm-intro"
198 #define MAXMSGLIMIT 9999
200 #define BASELINELEN 72
201 #define BASEMAXINLINE 256
204 #define MAX_DATETIME_FORMAT 512
205 #define MAX_NUM_CID_CONTEXTS 10
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 #define ERROR_MAILBOX_FULL -200
238 OPT_SILENT = (1 << 0),
239 OPT_BUSY_GREETING = (1 << 1),
240 OPT_UNAVAIL_GREETING = (1 << 2),
241 OPT_RECORDGAIN = (1 << 3),
242 OPT_PREPEND_MAILBOX = (1 << 4),
243 OPT_AUTOPLAY = (1 << 6),
244 OPT_DTMFEXIT = (1 << 7),
248 OPT_ARG_RECORDGAIN = 0,
249 OPT_ARG_PLAYFOLDER = 1,
250 OPT_ARG_DTMFEXIT = 2,
251 /* This *must* be the last value in this enum! */
252 OPT_ARG_ARRAY_SIZE = 3,
255 AST_APP_OPTIONS(vm_app_options, {
256 AST_APP_OPTION('s', OPT_SILENT),
257 AST_APP_OPTION('b', OPT_BUSY_GREETING),
258 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
259 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
260 AST_APP_OPTION_ARG('d', OPT_DTMFEXIT, OPT_ARG_DTMFEXIT),
261 AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
262 AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
265 static int load_config(int reload);
267 /*! \page vmlang Voicemail Language Syntaxes Supported
269 \par Syntaxes supported, not really language codes.
276 \arg \b pt - Portuguese
277 \arg \b pt_BR - Portuguese (Brazil)
279 \arg \b no - Norwegian
281 \arg \b tw - Chinese (Taiwan)
282 \arg \b ua - Ukrainian
284 German requires the following additional soundfile:
285 \arg \b 1F einE (feminine)
287 Spanish requires the following additional soundfile:
288 \arg \b 1M un (masculine)
290 Dutch, Portuguese & Spanish require the following additional soundfiles:
291 \arg \b vm-INBOXs singular of 'new'
292 \arg \b vm-Olds singular of 'old/heard/read'
295 \arg \b vm-INBOX nieuwe (nl)
296 \arg \b vm-Old oude (nl)
299 \arg \b vm-new-a 'new', feminine singular accusative
300 \arg \b vm-new-e 'new', feminine plural accusative
301 \arg \b vm-new-ych 'new', feminine plural genitive
302 \arg \b vm-old-a 'old', feminine singular accusative
303 \arg \b vm-old-e 'old', feminine plural accusative
304 \arg \b vm-old-ych 'old', feminine plural genitive
305 \arg \b digits/1-a 'one', not always same as 'digits/1'
306 \arg \b digits/2-ie 'two', not always same as 'digits/2'
309 \arg \b vm-nytt singular of 'new'
310 \arg \b vm-nya plural of 'new'
311 \arg \b vm-gammalt singular of 'old'
312 \arg \b vm-gamla plural of 'old'
313 \arg \b digits/ett 'one', not always same as 'digits/1'
316 \arg \b vm-ny singular of 'new'
317 \arg \b vm-nye plural of 'new'
318 \arg \b vm-gammel singular of 'old'
319 \arg \b vm-gamle plural of 'old'
327 Ukrainian requires the following additional soundfile:
328 \arg \b vm-nove 'nove'
329 \arg \b vm-stare 'stare'
330 \arg \b digits/ua/1e 'odne'
332 Italian requires the following additional soundfile:
336 \arg \b vm-nuovi new plural
337 \arg \b vm-vecchio old
338 \arg \b vm-vecchi old plural
340 Chinese (Taiwan) requires the following additional soundfile:
341 \arg \b vm-tong A class-word for call (tong1)
342 \arg \b vm-ri A class-word for day (ri4)
343 \arg \b vm-you You (ni3)
344 \arg \b vm-haveno Have no (mei2 you3)
345 \arg \b vm-have Have (you3)
346 \arg \b vm-listen To listen (yao4 ting1)
349 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
350 spelled among others when you have to change folder. For the above reasons, vm-INBOX
351 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
360 unsigned char iobuf[BASEMAXINLINE];
363 /*! Structure for linked list of users
364 * Use ast_vm_user_destroy() to free one of these structures. */
366 char context[AST_MAX_CONTEXT]; /*!< Voicemail context */
367 char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
368 char password[80]; /*!< Secret pin code, numbers only */
369 char fullname[80]; /*!< Full name, for directory app */
370 char email[80]; /*!< E-mail address */
371 char pager[80]; /*!< E-mail address to pager (no attachment) */
372 char serveremail[80]; /*!< From: Mail address */
373 char mailcmd[160]; /*!< Configurable mail command */
374 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
375 char zonetag[80]; /*!< Time zone */
378 char uniqueid[20]; /*!< Unique integer identifier */
380 char attachfmt[20]; /*!< Attachment format */
381 unsigned int flags; /*!< VM_ flags */
383 int maxmsg; /*!< Maximum number of msgs per folder for this mailbox */
384 int maxdeletedmsg; /*!< Maximum number of deleted msgs saved for this mailbox */
385 int maxsecs; /*!< Maximum number of seconds per message for this mailbox */
387 char imapuser[80]; /*!< IMAP server login */
388 char imappassword[80]; /*!< IMAP server password if authpassword not defined */
390 double volgain; /*!< Volume gain for voicemails sent via email */
391 AST_LIST_ENTRY(ast_vm_user) list;
394 /*! Voicemail time zones */
396 AST_LIST_ENTRY(vm_zone) list;
399 char msg_format[512];
402 /*! Voicemail mailbox state */
406 char curdir[PATH_MAX];
407 char vmbox[PATH_MAX];
419 int updated; /*!< decremented on each mail check until 1 -allows delay */
421 MAILSTREAM *mailstream;
423 char imapuser[80]; /*!< IMAP server login */
425 unsigned int quota_limit;
426 unsigned int quota_usage;
427 struct vm_state *persist_vms;
432 static char odbc_database[80];
433 static char odbc_table[80];
434 #define RETRIEVE(a,b,c,d) retrieve_file(a,b)
435 #define DISPOSE(a,b) remove_file(a,b)
436 #define STORE(a,b,c,d,e,f,g,h,i) store_file(a,b,c,d)
437 #define EXISTS(a,b,c,d) (message_exists(a,b))
438 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
439 #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
440 #define DELETE(a,b,c) (delete_file(a,b))
443 #define RETRIEVE(a,b,c,d) (imap_retrieve_file(a,b,c,d ))
444 #define DISPOSE(a,b) (imap_remove_file(a,b))
445 #define STORE(a,b,c,d,e,f,g,h,i) (imap_store_file(a,b,c,d,e,f,g,h,i))
446 #define EXISTS(a,b,c,d) (ast_fileexists(c, NULL, d) > 0)
447 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
448 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
449 #define IMAP_DELETE(a,b,c,d) (vm_imap_delete(b,d))
450 #define DELETE(a,b,c) (vm_delete(c))
452 #define RETRIEVE(a,b,c,d)
454 #define STORE(a,b,c,d,e,f,g,h,i)
455 #define EXISTS(a,b,c,d) (ast_fileexists(c, NULL, d) > 0)
456 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
457 #define COPY(a,b,c,d,e,f,g,h) (copy_plain_file(g,h));
458 #define DELETE(a,b,c) (vm_delete(c))
462 static char VM_SPOOL_DIR[PATH_MAX];
464 static char ext_pass_cmd[128];
468 #define PWDCHANGE_INTERNAL (1 << 1)
469 #define PWDCHANGE_EXTERNAL (1 << 2)
470 static int pwdchange = PWDCHANGE_INTERNAL;
473 #define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
476 # define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
478 # define tdesc "Comedian Mail (Voicemail System)"
482 static char userscontext[AST_MAX_EXTENSION] = "default";
484 static char *addesc = "Comedian Mail";
486 static char *synopsis_vm = "Leave a Voicemail message";
488 static char *descrip_vm =
489 " VoiceMail(mailbox[@context][&mailbox[@context]][...][,options]): This\n"
490 "application allows the calling party to leave a message for the specified\n"
491 "list of mailboxes. When multiple mailboxes are specified, the greeting will\n"
492 "be taken from the first mailbox specified. Dialplan execution will stop if the\n"
493 "specified mailbox does not exist.\n"
494 " The Voicemail application will exit if any of the following DTMF digits are\n"
496 " 0 - Jump to the 'o' extension in the current dialplan context.\n"
497 " * - Jump to the 'a' extension in the current dialplan context.\n"
498 " This application will set the following channel variable upon completion:\n"
499 " VMSTATUS - This indicates the status of the execution of the VoiceMail\n"
500 " application. The possible values are:\n"
501 " SUCCESS | USEREXIT | FAILED\n\n"
503 " b - Play the 'busy' greeting to the calling party.\n"
504 " d([c]) - Accept digits for a new extension in context c, if played during\n"
505 " the greeting. Context defaults to the current context.\n"
506 " g(#) - Use the specified amount of gain when recording the voicemail\n"
507 " message. The units are whole-number decibels (dB).\n"
508 " s - Skip the playback of instructions for leaving a message to the\n"
510 " u - Play the 'unavailable' greeting.\n";
512 static char *synopsis_vmain = "Check Voicemail messages";
514 static char *descrip_vmain =
515 " VoiceMailMain([mailbox][@context][,options]): This application allows the\n"
516 "calling party to check voicemail messages. A specific mailbox, and optional\n"
517 "corresponding context, may be specified. If a mailbox is not provided, the\n"
518 "calling party will be prompted to enter one. If a context is not specified,\n"
519 "the 'default' context will be used.\n\n"
521 " p - Consider the mailbox parameter as a prefix to the mailbox that\n"
522 " is entered by the caller.\n"
523 " g(#) - Use the specified amount of gain when recording a voicemail\n"
524 " message. The units are whole-number decibels (dB).\n"
525 " s - Skip checking the passcode for the mailbox.\n"
526 " a(#) - Skip folder prompt and go directly to folder specified.\n"
527 " Defaults to INBOX\n";
529 static char *synopsis_vm_box_exists =
530 "Check to see if Voicemail mailbox exists";
532 static char *descrip_vm_box_exists =
533 " MailboxExists(mailbox[@context][,options]): Check to see if the specified\n"
534 "mailbox exists. If no voicemail context is specified, the 'default' context\n"
536 " This application will set the following channel variable upon completion:\n"
537 " VMBOXEXISTSSTATUS - This will contain the status of the execution of the\n"
538 " MailboxExists application. Possible values include:\n"
539 " SUCCESS | FAILED\n\n"
540 " Options: (none)\n";
542 static char *synopsis_vmauthenticate = "Authenticate with Voicemail passwords";
544 static char *descrip_vmauthenticate =
545 " VMAuthenticate([mailbox][@context][,options]): This application behaves the\n"
546 "same way as the Authenticate application, but the passwords are taken from\n"
548 " If the mailbox is specified, only that mailbox's password will be considered\n"
549 "valid. If the mailbox is not specified, the channel variable AUTH_MAILBOX will\n"
550 "be set with the authenticated mailbox.\n\n"
552 " s - Skip playing the initial prompts.\n";
554 /* Leave a message */
555 static char *app = "VoiceMail";
557 /* Check mail, control, etc */
558 static char *app2 = "VoiceMailMain";
560 static char *app3 = "MailboxExists";
561 static char *app4 = "VMAuthenticate";
563 static AST_LIST_HEAD_STATIC(users, ast_vm_user);
564 static AST_LIST_HEAD_STATIC(zones, vm_zone);
565 static int maxsilence;
567 static int maxdeletedmsg;
568 static int silencethreshold = 128;
569 static char serveremail[80];
570 static char mailcmd[160]; /* Configurable mail cmd */
571 static char externnotify[160];
572 static struct ast_smdi_interface *smdi_iface = NULL;
573 static char vmfmts[80];
574 static double volgain;
575 static int vmminsecs;
576 static int vmmaxsecs;
579 static int maxlogins;
581 /*! Poll mailboxes for changes since there is something external to
582 * app_voicemail that may change them. */
583 static unsigned int poll_mailboxes;
585 /*! Polling frequency */
586 static unsigned int poll_freq;
587 /*! By default, poll every 30 seconds */
588 #define DEFAULT_POLL_FREQ 30
590 AST_MUTEX_DEFINE_STATIC(poll_lock);
591 static ast_cond_t poll_cond = PTHREAD_COND_INITIALIZER;
592 static pthread_t poll_thread = AST_PTHREADT_NULL;
593 static unsigned char poll_thread_run;
595 /*! Subscription to ... MWI event subscriptions */
596 static struct ast_event_sub *mwi_sub_sub;
597 /*! Subscription to ... MWI event un-subscriptions */
598 static struct ast_event_sub *mwi_unsub_sub;
601 * \brief An MWI subscription
603 * This is so we can keep track of which mailboxes are subscribed to.
604 * This way, we know which mailboxes to poll when the pollmailboxes
605 * option is being used.
608 AST_RWLIST_ENTRY(mwi_sub) entry;
615 static AST_RWLIST_HEAD_STATIC(mwi_subs, mwi_sub);
617 /* custom audio control prompts for voicemail playback */
618 static char listen_control_forward_key[12];
619 static char listen_control_reverse_key[12];
620 static char listen_control_pause_key[12];
621 static char listen_control_restart_key[12];
622 static char listen_control_stop_key[12];
624 /* custom password sounds */
625 static char vm_password[80] = "vm-password";
626 static char vm_newpassword[80] = "vm-newpassword";
627 static char vm_passchanged[80] = "vm-passchanged";
628 static char vm_reenterpassword[80] = "vm-reenterpassword";
629 static char vm_mismatch[80] = "vm-mismatch";
631 static struct ast_flags globalflags = {0};
633 static int saydurationminfo;
635 static char dialcontext[AST_MAX_CONTEXT] = "";
636 static char callcontext[AST_MAX_CONTEXT] = "";
637 static char exitcontext[AST_MAX_CONTEXT] = "";
639 static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
642 static char *emailbody = NULL;
643 static char *emailsubject = NULL;
644 static char *pagerbody = NULL;
645 static char *pagersubject = NULL;
646 static char fromstring[100];
647 static char pagerfromstring[100];
648 static char emailtitle[100];
649 static char charset[32] = "ISO-8859-1";
651 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
652 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
653 static int adsiver = 1;
654 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
656 /* Forward declarations - generic */
657 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
658 static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msg, int option, signed char record_gain);
659 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
660 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
661 char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
662 signed char record_gain, struct vm_state *vms);
663 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
664 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
665 static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msgnum, long duration, char *fmt, char *cidnum, char *cidname);
666 static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, char *cidnum, char *cidname, char *attach, char *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category, int imap);
667 static void apply_options(struct ast_vm_user *vmu, const char *options);
668 static int is_valid_dtmf(const char *key);
670 #if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
671 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
676 static void populate_defaults(struct ast_vm_user *vmu)
678 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
679 if (saydurationminfo)
680 vmu->saydurationm = saydurationminfo;
681 ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
682 ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
683 ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
685 vmu->maxsecs = vmmaxsecs;
687 vmu->maxmsg = maxmsg;
689 vmu->maxdeletedmsg = maxdeletedmsg;
690 vmu->volgain = volgain;
693 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
696 if (!strcasecmp(var, "attach")) {
697 ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
698 } else if (!strcasecmp(var, "attachfmt")) {
699 ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
700 } else if (!strcasecmp(var, "serveremail")) {
701 ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
702 } else if (!strcasecmp(var, "language")) {
703 ast_copy_string(vmu->language, value, sizeof(vmu->language));
704 } else if (!strcasecmp(var, "tz")) {
705 ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
707 } else if (!strcasecmp(var, "imapuser")) {
708 ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
709 } else if (!strcasecmp(var, "imappassword")) {
710 ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
712 } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
713 ast_set2_flag(vmu, ast_true(value), VM_DELETE);
714 } else if (!strcasecmp(var, "saycid")) {
715 ast_set2_flag(vmu, ast_true(value), VM_SAYCID);
716 } else if (!strcasecmp(var, "sendvoicemail")) {
717 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL);
718 } else if (!strcasecmp(var, "review")) {
719 ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
720 } else if (!strcasecmp(var, "tempgreetwarn")) {
721 ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);
722 } else if (!strcasecmp(var, "operator")) {
723 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);
724 } else if (!strcasecmp(var, "envelope")) {
725 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);
726 } else if (!strcasecmp(var, "moveheard")) {
727 ast_set2_flag(vmu, ast_true(value), VM_MOVEHEARD);
728 } else if (!strcasecmp(var, "sayduration")) {
729 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);
730 } else if (!strcasecmp(var, "saydurationm")) {
731 if (sscanf(value, "%d", &x) == 1) {
732 vmu->saydurationm = x;
734 ast_log(LOG_WARNING, "Invalid min duration for say duration\n");
736 } else if (!strcasecmp(var, "forcename")) {
737 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);
738 } else if (!strcasecmp(var, "forcegreetings")) {
739 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);
740 } else if (!strcasecmp(var, "callback")) {
741 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
742 } else if (!strcasecmp(var, "dialout")) {
743 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
744 } else if (!strcasecmp(var, "exitcontext")) {
745 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
746 } else if (!strcasecmp(var, "maxmessage") || !strcasecmp(var, "maxsecs")) {
747 if (vmu->maxsecs <= 0) {
748 ast_log(LOG_WARNING, "Invalid max message length of %s. Using global value %d\n", value, vmmaxsecs);
749 vmu->maxsecs = vmmaxsecs;
751 vmu->maxsecs = atoi(value);
753 if (!strcasecmp(var, "maxmessage"))
754 ast_log(LOG_WARNING, "Option 'maxmessage' has been deprecated in favor of 'maxsecs'. Please make that change in your voicemail config.\n");
755 } else if (!strcasecmp(var, "maxmsg")) {
756 vmu->maxmsg = atoi(value);
757 if (vmu->maxmsg <= 0) {
758 ast_log(LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %d\n", value, MAXMSG);
759 vmu->maxmsg = MAXMSG;
760 } else if (vmu->maxmsg > MAXMSGLIMIT) {
761 ast_log(LOG_WARNING, "Maximum number of messages per folder is %d. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
762 vmu->maxmsg = MAXMSGLIMIT;
764 } else if (!strcasecmp(var, "backupdeleted")) {
765 if (sscanf(value, "%d", &x) == 1)
766 vmu->maxdeletedmsg = x;
767 else if (ast_true(value))
768 vmu->maxdeletedmsg = MAXMSG;
770 vmu->maxdeletedmsg = 0;
772 if (vmu->maxdeletedmsg < 0) {
773 ast_log(LOG_WARNING, "Invalid number of deleted messages saved per mailbox backupdeleted=%s. Using default value %d\n", value, MAXMSG);
774 vmu->maxdeletedmsg = MAXMSG;
775 } else if (vmu->maxdeletedmsg > MAXMSGLIMIT) {
776 ast_log(LOG_WARNING, "Maximum number of deleted messages saved per mailbox is %d. Cannot accept value backupdeleted=%s\n", MAXMSGLIMIT, value);
777 vmu->maxdeletedmsg = MAXMSGLIMIT;
779 } else if (!strcasecmp(var, "volgain")) {
780 sscanf(value, "%lf", &vmu->volgain);
781 } else if (!strcasecmp(var, "options")) {
782 apply_options(vmu, value);
786 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
789 if (!ast_strlen_zero(vmu->uniqueid)) {
790 res = ast_update_realtime("voicemail", "uniqueid", vmu->uniqueid, "password", password, NULL);
792 ast_copy_string(vmu->password, password, sizeof(vmu->password));
802 static void apply_options(struct ast_vm_user *vmu, const char *options)
803 { /* Destructively Parse options and apply */
807 stringp = ast_strdupa(options);
808 while ((s = strsep(&stringp, "|"))) {
810 if ((var = strsep(&value, "=")) && value) {
811 apply_option(vmu, var, value);
816 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
818 struct ast_variable *tmp;
821 if (!strcasecmp(tmp->name, "vmsecret")) {
822 ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
823 } else if (!strcasecmp(tmp->name, "secret") || !strcasecmp(tmp->name, "password")) { /* don't overwrite vmsecret if it exists */
824 if (ast_strlen_zero(retval->password))
825 ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
826 } else if (!strcasecmp(tmp->name, "uniqueid")) {
827 ast_copy_string(retval->uniqueid, tmp->value, sizeof(retval->uniqueid));
828 } else if (!strcasecmp(tmp->name, "pager")) {
829 ast_copy_string(retval->pager, tmp->value, sizeof(retval->pager));
830 } else if (!strcasecmp(tmp->name, "email")) {
831 ast_copy_string(retval->email, tmp->value, sizeof(retval->email));
832 } else if (!strcasecmp(tmp->name, "fullname")) {
833 ast_copy_string(retval->fullname, tmp->value, sizeof(retval->fullname));
834 } else if (!strcasecmp(tmp->name, "context")) {
835 ast_copy_string(retval->context, tmp->value, sizeof(retval->context));
837 } else if (!strcasecmp(tmp->name, "imapuser")) {
838 ast_copy_string(retval->imapuser, tmp->value, sizeof(retval->imapuser));
839 } else if (!strcasecmp(tmp->name, "imappassword")) {
840 ast_copy_string(retval->imappassword, tmp->value, sizeof(retval->imappassword));
843 apply_option(retval, tmp->name, tmp->value);
848 static int is_valid_dtmf(const char *key)
851 char *local_key = ast_strdupa(key);
853 for (i = 0; i < strlen(key); ++i) {
854 if (!strchr(VALID_DTMF, *local_key)) {
855 ast_log(LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
863 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
865 struct ast_variable *var;
866 struct ast_vm_user *retval;
868 if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
870 ast_set_flag(retval, VM_ALLOCED);
872 memset(retval, 0, sizeof(*retval));
874 ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
875 populate_defaults(retval);
876 if (!context && ast_test_flag((&globalflags), VM_SEARCH))
877 var = ast_load_realtime("voicemail", "mailbox", mailbox, NULL);
879 var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, NULL);
881 apply_options_full(retval, var);
882 ast_variables_destroy(var);
892 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
894 /* This function could be made to generate one from a database, too */
895 struct ast_vm_user *vmu = NULL, *cur;
896 AST_LIST_LOCK(&users);
898 if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
901 AST_LIST_TRAVERSE(&users, cur, list) {
902 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
904 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
908 /* Make a copy, so that on a reload, we have no race */
909 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
910 memcpy(vmu, cur, sizeof(*vmu));
911 ast_set2_flag(vmu, !ivm, VM_ALLOCED);
912 AST_LIST_NEXT(vmu, list) = NULL;
915 vmu = find_user_realtime(ivm, context, mailbox);
916 AST_LIST_UNLOCK(&users);
920 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
922 /* This function could be made to generate one from a database, too */
923 struct ast_vm_user *cur;
925 AST_LIST_LOCK(&users);
926 AST_LIST_TRAVERSE(&users, cur, list) {
927 if ((!context || !strcasecmp(context, cur->context)) &&
928 (!strcasecmp(mailbox, cur->mailbox)))
932 ast_copy_string(cur->password, newpass, sizeof(cur->password));
935 AST_LIST_UNLOCK(&users);
939 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
941 struct ast_config *cfg = NULL;
942 struct ast_variable *var = NULL;
943 struct ast_category *cat = NULL;
944 char *category = NULL, *value = NULL, *new = NULL;
945 const char *tmp = NULL;
946 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
947 if (!change_password_realtime(vmu, newpassword))
950 /* check voicemail.conf */
951 if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags))) {
952 while ((category = ast_category_browse(cfg, category))) {
953 if (!strcasecmp(category, vmu->context)) {
954 if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
955 ast_log(LOG_WARNING, "We could not find the mailbox.\n");
958 value = strstr(tmp, ",");
960 ast_log(LOG_WARNING, "variable has bad format.\n");
963 new = alloca(strlen(value) + strlen(newpassword) + 1);
964 sprintf(new, "%s%s", newpassword, value);
965 if (!(cat = ast_category_get(cfg, category))) {
966 ast_log(LOG_WARNING, "Failed to get category structure.\n");
969 ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
972 /* save the results */
973 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
974 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
975 config_text_file_save(VOICEMAIL_CONFIG, cfg, "AppVoicemail");
979 /* check users.conf and update the password stored for the mailbox*/
980 /* if no vmsecret entry exists create one. */
981 if ((cfg = ast_config_load("users.conf", config_flags))) {
982 ast_debug(4, "we are looking for %s\n", vmu->mailbox);
983 while ((category = ast_category_browse(cfg, category))) {
984 ast_debug(4, "users.conf: %s\n", category);
985 if (!strcasecmp(category, vmu->mailbox)) {
986 if (!(tmp = ast_variable_retrieve(cfg, category, "vmsecret"))) {
987 ast_debug(3, "looks like we need to make vmsecret!\n");
988 var = ast_variable_new("vmsecret", newpassword, "");
990 new = alloca(strlen(newpassword) + 1);
991 sprintf(new, "%s", newpassword);
992 if (!(cat = ast_category_get(cfg, category))) {
993 ast_debug(4, "failed to get category!\n");
997 ast_variable_update(cat, "vmsecret", new, NULL, 0);
999 ast_variable_append(cat, var);
1002 /* save the results and clean things up */
1003 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1004 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1005 config_text_file_save("users.conf", cfg, "AppVoicemail");
1009 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
1012 snprintf(buf, sizeof(buf), "%s %s %s %s", ext_pass_cmd, vmu->context, vmu->mailbox, newpassword);
1013 if (!ast_safe_system(buf)) {
1014 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1015 /* Reset the password in memory, too */
1016 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1020 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
1022 return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
1026 static int make_gsm_file(char *dest, size_t len, char *imapuser, char *dir, int num)
1029 if ((res = ast_mkdir(dir, 01777))) {
1030 ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dir, strerror(res));
1031 return snprintf(dest, len, "%s/msg%04d", dir, num);
1033 return snprintf(dest, len, "%s/msg%04d", dir, num);
1036 static void vm_imap_delete(int msgnum, struct vm_state *vms)
1038 unsigned long messageNum = 0;
1041 /* find real message number based on msgnum */
1042 /* this may be an index into vms->msgArray based on the msgnum. */
1044 messageNum = vms->msgArray[msgnum];
1045 if (messageNum == 0) {
1046 ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n", msgnum, messageNum);
1049 ast_debug(3, "deleting msgnum %d, which is mailbox message %lu\n", msgnum, messageNum);
1050 /* delete message */
1051 snprintf(arg, sizeof(arg), "%lu", messageNum);
1052 mail_setflag(vms->mailstream, arg, "\\DELETED");
1056 static int make_file(char *dest, int len, char *dir, int num)
1058 return snprintf(dest, len, "%s/msg%04d", dir, num);
1061 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
1062 * \param dest String. base directory.
1063 * \param len Length of dest.
1064 * \param context String. Ignored if is null or empty string.
1065 * \param ext String. Ignored if is null or empty string.
1066 * \param folder String. Ignored if is null or empty string.
1067 * \return -1 on failure, 0 on success.
1069 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
1071 mode_t mode = VOICEMAIL_DIR_MODE;
1074 make_dir(dest, len, context, ext, folder);
1075 if ((res = ast_mkdir(dest, mode))) {
1076 ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1082 /*! \brief Lock file path
1083 only return failure if ast_lock_path returns 'timeout',
1084 not if the path does not exist or any other reason
1086 static int vm_lock_path(const char *path)
1088 switch (ast_lock_path(path)) {
1089 case AST_LOCK_TIMEOUT:
1098 struct generic_prepare_struct {
1104 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
1106 struct generic_prepare_struct *gps = data;
1110 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1111 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1112 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1115 res = SQLPrepare(stmt, (unsigned char *)gps->sql, SQL_NTS);
1116 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1117 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", gps->sql);
1118 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1121 for (i = 0; i < gps->argc; i++)
1122 SQLBindParameter(stmt, i + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->argv[i]), 0, gps->argv[i], 0, NULL);
1127 static int retrieve_file(char *dir, int msgnum)
1133 void *fdm = MAP_FAILED;
1134 SQLSMALLINT colcount = 0;
1141 SQLSMALLINT datatype;
1142 SQLSMALLINT decimaldigits;
1143 SQLSMALLINT nullable;
1149 char full_fn[PATH_MAX];
1151 char *argv[] = { dir, msgnums };
1152 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
1154 struct odbc_obj *obj;
1155 obj = ast_odbc_request_obj(odbc_database, 0);
1157 ast_copy_string(fmt, vmfmts, sizeof(fmt));
1158 c = strchr(fmt, '|');
1161 if (!strcasecmp(fmt, "wav49"))
1163 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
1165 make_file(fn, sizeof(fn), dir, msgnum);
1167 ast_copy_string(fn, dir, sizeof(fn));
1168 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
1170 if (!(f = fopen(full_fn, "w+"))) {
1171 ast_log(LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
1175 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
1176 snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?", odbc_table);
1177 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1179 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1180 ast_odbc_release_obj(obj);
1183 res = SQLFetch(stmt);
1184 if (res == SQL_NO_DATA) {
1185 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1186 ast_odbc_release_obj(obj);
1189 else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1190 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
1191 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1192 ast_odbc_release_obj(obj);
1195 fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
1197 ast_log(LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
1198 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1199 ast_odbc_release_obj(obj);
1202 res = SQLNumResultCols(stmt, &colcount);
1203 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1204 ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
1205 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1206 ast_odbc_release_obj(obj);
1210 fprintf(f, "[message]\n");
1211 for (x = 0; x < colcount; x++) {
1213 collen = sizeof(coltitle);
1214 res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen,
1215 &datatype, &colsize, &decimaldigits, &nullable);
1216 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1217 ast_log(LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
1218 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1219 ast_odbc_release_obj(obj);
1222 if (!strcasecmp(coltitle, "recording")) {
1224 res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
1228 lseek(fd, fdlen - 1, SEEK_SET);
1229 if (write(fd, tmp, 1) != 1) {
1234 /* Read out in small chunks */
1235 for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
1236 if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
1237 ast_log(LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
1238 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1239 ast_odbc_release_obj(obj);
1242 res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
1243 munmap(fdm, CHUNKSIZE);
1244 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1245 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1247 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1248 ast_odbc_release_obj(obj);
1253 truncate(full_fn, fdlen);
1256 res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
1257 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1258 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1259 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1260 ast_odbc_release_obj(obj);
1263 if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
1264 fprintf(f, "%s=%s\n", coltitle, rowdata);
1267 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1268 ast_odbc_release_obj(obj);
1270 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1279 static int remove_file(char *dir, int msgnum)
1282 char full_fn[PATH_MAX];
1286 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
1287 make_file(fn, sizeof(fn), dir, msgnum);
1289 ast_copy_string(fn, dir, sizeof(fn));
1290 ast_filedelete(fn, NULL);
1291 if (ast_check_realtime("voicemail_data")) {
1292 ast_destroy_realtime("voicemail_data", "filename", fn, NULL);
1294 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
1299 static int last_message_index(struct ast_vm_user *vmu, char *dir)
1306 char *argv[] = { dir };
1307 struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
1309 struct odbc_obj *obj;
1310 obj = ast_odbc_request_obj(odbc_database, 0);
1312 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?", odbc_table);
1313 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1315 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1316 ast_odbc_release_obj(obj);
1319 res = SQLFetch(stmt);
1320 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1321 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
1322 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1323 ast_odbc_release_obj(obj);
1326 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
1327 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1328 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1329 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1330 ast_odbc_release_obj(obj);
1333 if (sscanf(rowdata, "%d", &x) != 1)
1334 ast_log(LOG_WARNING, "Failed to read message count!\n");
1335 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1336 ast_odbc_release_obj(obj);
1338 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1343 static int message_exists(char *dir, int msgnum)
1351 char *argv[] = { dir, msgnums };
1352 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
1354 struct odbc_obj *obj;
1355 obj = ast_odbc_request_obj(odbc_database, 0);
1357 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
1358 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?", odbc_table);
1359 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1361 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1362 ast_odbc_release_obj(obj);
1365 res = SQLFetch(stmt);
1366 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1367 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
1368 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1369 ast_odbc_release_obj(obj);
1372 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
1373 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1374 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1375 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1376 ast_odbc_release_obj(obj);
1379 if (sscanf(rowdata, "%d", &x) != 1)
1380 ast_log(LOG_WARNING, "Failed to read message count!\n");
1381 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1382 ast_odbc_release_obj(obj);
1384 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1389 static int count_messages(struct ast_vm_user *vmu, char *dir)
1391 return last_message_index(vmu, dir) + 1;
1394 static void delete_file(char *sdir, int smsg)
1399 char *argv[] = { sdir, msgnums };
1400 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
1402 struct odbc_obj *obj;
1403 obj = ast_odbc_request_obj(odbc_database, 0);
1405 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1406 snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?", odbc_table);
1407 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1409 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1411 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1412 ast_odbc_release_obj(obj);
1414 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1418 static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailboxuser, char *dmailboxcontext)
1424 struct odbc_obj *obj;
1425 char *argv[] = { ddir, msgnumd, dmailboxuser, dmailboxcontext, sdir, msgnums };
1426 struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
1428 delete_file(ddir, dmsg);
1429 obj = ast_odbc_request_obj(odbc_database, 0);
1431 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1432 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
1433 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);
1434 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1436 ast_log(LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
1438 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1439 ast_odbc_release_obj(obj);
1441 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1445 static int store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum)
1450 void *fdm = MAP_FAILED;
1457 char full_fn[PATH_MAX];
1460 const char *context = "", *macrocontext = "", *callerid = "", *origtime = "", *duration = "";
1461 const char *category = "";
1462 struct ast_config *cfg = NULL;
1463 struct odbc_obj *obj;
1464 struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
1466 delete_file(dir, msgnum);
1467 obj = ast_odbc_request_obj(odbc_database, 0);
1469 ast_copy_string(fmt, vmfmts, sizeof(fmt));
1470 c = strchr(fmt, '|');
1473 if (!strcasecmp(fmt, "wav49"))
1475 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
1477 make_file(fn, sizeof(fn), dir, msgnum);
1479 ast_copy_string(fn, dir, sizeof(fn));
1480 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
1481 cfg = ast_config_load(full_fn, config_flags);
1482 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
1483 fd = open(full_fn, O_RDWR);
1485 ast_log(LOG_WARNING, "Open of sound file '%s' failed: %s\n", full_fn, strerror(errno));
1486 ast_odbc_release_obj(obj);
1490 context = ast_variable_retrieve(cfg, "message", "context");
1491 if (!context) context = "";
1492 macrocontext = ast_variable_retrieve(cfg, "message", "macrocontext");
1493 if (!macrocontext) macrocontext = "";
1494 callerid = ast_variable_retrieve(cfg, "message", "callerid");
1495 if (!callerid) callerid = "";
1496 origtime = ast_variable_retrieve(cfg, "message", "origtime");
1497 if (!origtime) origtime = "";
1498 duration = ast_variable_retrieve(cfg, "message", "duration");
1499 if (!duration) duration = "";
1500 category = ast_variable_retrieve(cfg, "message", "category");
1501 if (!category) category = "";
1503 fdlen = lseek(fd, 0, SEEK_END);
1504 lseek(fd, 0, SEEK_SET);
1505 printf("Length is %zd\n", fdlen);
1506 fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
1507 if (fdm == MAP_FAILED) {
1508 ast_log(LOG_WARNING, "Memory map failed!\n");
1509 ast_odbc_release_obj(obj);
1512 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1513 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1514 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1515 ast_odbc_release_obj(obj);
1518 if (!ast_strlen_zero(category))
1519 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,category) VALUES (?,?,?,?,?,?,?,?,?,?,?)", odbc_table);
1521 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext) VALUES (?,?,?,?,?,?,?,?,?,?)", odbc_table);
1522 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
1523 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1524 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1525 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1526 ast_odbc_release_obj(obj);
1529 len = fdlen; /* SQL_LEN_DATA_AT_EXEC(fdlen); */
1530 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
1531 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1532 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, fdlen, 0, (void *)fdm, fdlen, &len);
1533 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(context), 0, (void *)context, 0, NULL);
1534 SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(macrocontext), 0, (void *)macrocontext, 0, NULL);
1535 SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(callerid), 0, (void *)callerid, 0, NULL);
1536 SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(origtime), 0, (void *)origtime, 0, NULL);
1537 SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(duration), 0, (void *)duration, 0, NULL);
1538 SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxuser), 0, (void *)mailboxuser, 0, NULL);
1539 SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxcontext), 0, (void *)mailboxcontext, 0, NULL);
1540 if (!ast_strlen_zero(category))
1541 SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(category), 0, (void *)category, 0, NULL);
1542 res = ast_odbc_smart_execute(obj, stmt);
1543 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1544 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1545 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1546 ast_odbc_release_obj(obj);
1549 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1550 ast_odbc_release_obj(obj);
1552 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1555 ast_config_destroy(cfg);
1556 if (fdm != MAP_FAILED)
1563 static void rename_file(char *sdir, int smsg, char *mailboxuser, char *mailboxcontext, char *ddir, int dmsg)
1569 struct odbc_obj *obj;
1570 char *argv[] = { ddir, msgnumd, mailboxuser, mailboxcontext, sdir, msgnums };
1571 struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
1573 delete_file(ddir, dmsg);
1574 obj = ast_odbc_request_obj(odbc_database, 0);
1576 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1577 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
1578 snprintf(sql, sizeof(sql), "UPDATE %s SET dir=?, msgnum=?, mailboxuser=?, mailboxcontext=? WHERE dir=? AND msgnum=?", odbc_table);
1579 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1581 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1583 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1584 ast_odbc_release_obj(obj);
1586 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1591 #ifndef IMAP_STORAGE
1592 static int count_messages(struct ast_vm_user *vmu, char *dir)
1594 /* Find all .txt files - even if they are not in sequence from 0000 */
1598 struct dirent *vment = NULL;
1600 if (vm_lock_path(dir))
1601 return ERROR_LOCK_PATH;
1603 if ((vmdir = opendir(dir))) {
1604 while ((vment = readdir(vmdir))) {
1605 if (strlen(vment->d_name) > 7 && !strncmp(vment->d_name + 7, ".txt", 4))
1610 ast_unlock_path(dir);
1615 static void rename_file(char *sfn, char *dfn)
1617 char stxt[PATH_MAX];
1618 char dtxt[PATH_MAX];
1619 ast_filerename(sfn, dfn, NULL);
1620 snprintf(stxt, sizeof(stxt), "%s.txt", sfn);
1621 snprintf(dtxt, sizeof(dtxt), "%s.txt", dfn);
1622 if (ast_check_realtime("voicemail_data")) {
1623 ast_update_realtime("voicemail_data", "filename", sfn, "filename", dfn, NULL);
1629 #ifndef IMAP_STORAGE
1631 * A negative return value indicates an error.
1632 * \note Should always be called with a lock already set on dir.
1634 static int last_message_index(struct ast_vm_user *vmu, char *dir)
1637 unsigned char map[MAXMSGLIMIT] = "";
1639 struct dirent *msgdirent;
1642 /* Reading the entire directory into a file map scales better than
1643 * doing a stat repeatedly on a predicted sequence. I suspect this
1644 * is partially due to stat(2) internally doing a readdir(2) itself to
1645 * find each file. */
1646 msgdir = opendir(dir);
1647 while ((msgdirent = readdir(msgdir))) {
1648 if (sscanf(msgdirent->d_name, "msg%d", &msgdirint) == 1 && msgdirint < MAXMSGLIMIT)
1653 for (x = 0; x < vmu->maxmsg; x++) {
1661 #endif /*#ifndef IMAP_STORAGE*/
1662 #endif /*#else of #ifdef ODBC_STORAGE*/
1664 static int copy(char *infile, char *outfile)
1672 #ifdef HARDLINK_WHEN_POSSIBLE
1673 /* Hard link if possible; saves disk space & is faster */
1674 if (link(infile, outfile)) {
1676 if ((ifd = open(infile, O_RDONLY)) < 0) {
1677 ast_log(LOG_WARNING, "Unable to open %s in read-only mode\n", infile);
1680 if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, VOICEMAIL_FILE_MODE)) < 0) {
1681 ast_log(LOG_WARNING, "Unable to open %s in write-only mode\n", outfile);
1686 len = read(ifd, buf, sizeof(buf));
1688 ast_log(LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
1694 res = write(ofd, buf, len);
1695 if (errno == ENOMEM || errno == ENOSPC || res != len) {
1696 ast_log(LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno));
1706 #ifdef HARDLINK_WHEN_POSSIBLE
1708 /* Hard link succeeded */
1714 static void copy_plain_file(char *frompath, char *topath)
1716 char frompath2[PATH_MAX], topath2[PATH_MAX];
1717 struct ast_variable *tmp, *var = NULL;
1718 const char *origmailbox = NULL, *context = NULL, *macrocontext = NULL, *exten = NULL, *priority = NULL, *callerchan = NULL, *callerid = NULL, *origdate = NULL, *origtime = NULL, *category = NULL, *duration = NULL;
1719 ast_filecopy(frompath, topath, NULL);
1720 snprintf(frompath2, sizeof(frompath2), "%s.txt", frompath);
1721 snprintf(topath2, sizeof(topath2), "%s.txt", topath);
1722 if (ast_check_realtime("voicemail_data")) {
1723 var = ast_load_realtime("voicemail_data", "filename", frompath, NULL);
1724 /* This cycle converts ast_variable linked list, to va_list list of arguments, may be there is a better way to do it? */
1725 for (tmp = var; tmp; tmp = tmp->next) {
1726 if (!strcasecmp(tmp->name, "origmailbox")) {
1727 origmailbox = tmp->value;
1728 } else if (!strcasecmp(tmp->name, "context")) {
1729 context = tmp->value;
1730 } else if (!strcasecmp(tmp->name, "macrocontext")) {
1731 macrocontext = tmp->value;
1732 } else if (!strcasecmp(tmp->name, "exten")) {
1734 } else if (!strcasecmp(tmp->name, "priority")) {
1735 priority = tmp->value;
1736 } else if (!strcasecmp(tmp->name, "callerchan")) {
1737 callerchan = tmp->value;
1738 } else if (!strcasecmp(tmp->name, "callerid")) {
1739 callerid = tmp->value;
1740 } else if (!strcasecmp(tmp->name, "origdate")) {
1741 origdate = tmp->value;
1742 } else if (!strcasecmp(tmp->name, "origtime")) {
1743 origtime = tmp->value;
1744 } else if (!strcasecmp(tmp->name, "category")) {
1745 category = tmp->value;
1746 } else if (!strcasecmp(tmp->name, "duration")) {
1747 duration = tmp->value;
1750 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);
1752 copy(frompath2, topath2);
1753 ast_variables_destroy(var);
1756 static int vm_delete(char *file)
1761 txtsize = (strlen(file) + 5) * sizeof(char);
1762 txt = alloca(txtsize);
1763 /* Sprintf here would safe because we alloca'd exactly the right length,
1764 * but trying to eliminate all sprintf's anyhow
1766 if (ast_check_realtime("voicemail_data")) {
1767 ast_destroy_realtime("voicemail_data", "filename", file, NULL);
1769 snprintf(txt, txtsize, "%s.txt", file);
1771 return ast_filedelete(file, NULL);
1774 static int inbuf(struct baseio *bio, FILE *fi)
1781 if ((l = fread(bio->iobuf, 1, BASEMAXINLINE, fi)) <= 0) {
1795 static int inchar(struct baseio *bio, FILE *fi)
1797 if (bio->iocp >= bio->iolen) {
1798 if (!inbuf(bio, fi))
1802 return bio->iobuf[bio->iocp++];
1805 static int ochar(struct baseio *bio, int c, FILE *so)
1807 if (bio->linelength >= BASELINELEN) {
1808 if (fputs(eol, so) == EOF)
1814 if (putc(((unsigned char)c), so) == EOF)
1822 static int base_encode(char *filename, FILE *so)
1824 static const unsigned char dtable[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
1825 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
1826 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0',
1827 '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
1832 memset(&bio, 0, sizeof(bio));
1833 bio.iocp = BASEMAXINLINE;
1835 if (!(fi = fopen(filename, "rb"))) {
1836 ast_log(LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno));
1841 unsigned char igroup[3], ogroup[4];
1844 igroup[0] = igroup[1] = igroup[2] = 0;
1846 for (n = 0; n < 3; n++) {
1847 if ((c = inchar(&bio, fi)) == EOF) {
1852 igroup[n] = (unsigned char)c;
1856 ogroup[0] = dtable[igroup[0] >> 2];
1857 ogroup[1] = dtable[((igroup[0] & 3) << 4) | (igroup[1] >> 4)];
1858 ogroup[2] = dtable[((igroup[1] & 0xF) << 2) | (igroup[2] >> 6)];
1859 ogroup[3] = dtable[igroup[2] & 0x3F];
1868 for (i = 0; i < 4; i++)
1869 ochar(&bio, ogroup[i], so);
1875 if (fputs(eol, so) == EOF)
1881 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)
1884 /* Prepare variables for substitution in email body and subject */
1885 pbx_builtin_setvar_helper(ast, "VM_NAME", vmu->fullname);
1886 pbx_builtin_setvar_helper(ast, "VM_DUR", dur);
1887 snprintf(passdata, passdatasize, "%d", msgnum);
1888 pbx_builtin_setvar_helper(ast, "VM_MSGNUM", passdata);
1889 pbx_builtin_setvar_helper(ast, "VM_CONTEXT", context);
1890 pbx_builtin_setvar_helper(ast, "VM_MAILBOX", mailbox);
1891 pbx_builtin_setvar_helper(ast, "VM_CALLERID", ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, "Unknown Caller"));
1892 pbx_builtin_setvar_helper(ast, "VM_CIDNAME", (cidname ? cidname : "an unknown caller"));
1893 pbx_builtin_setvar_helper(ast, "VM_CIDNUM", (cidnum ? cidnum : "an unknown caller"));
1894 pbx_builtin_setvar_helper(ast, "VM_DATE", date);
1895 pbx_builtin_setvar_helper(ast, "VM_CATEGORY", category ? ast_strdupa(category) : "no category");
1898 static char *quote(const char *from, char *to, size_t len)
1902 for (; ptr < to + len - 1; from++) {
1905 else if (*from == '\0')
1909 if (ptr < to + len - 1)
1916 * fill in *tm for current time according to the proper timezone, if any.
1917 * Return tm so it can be used as a function argument.
1919 static const struct ast_tm *vmu_tm(const struct ast_vm_user *vmu, struct ast_tm *tm)
1921 const struct vm_zone *z = NULL;
1922 struct timeval t = ast_tvnow();
1924 /* Does this user have a timezone specified? */
1925 if (!ast_strlen_zero(vmu->zonetag)) {
1926 /* Find the zone in the list */
1927 AST_LIST_LOCK(&zones);
1928 AST_LIST_TRAVERSE(&zones, z, list) {
1929 if (!strcmp(z->name, vmu->zonetag))
1932 AST_LIST_UNLOCK(&zones);
1934 ast_localtime(&t, tm, z ? z->timezone : NULL);
1938 /*! \brief same as mkstemp, but return a FILE * */
1939 static FILE *vm_mkftemp(char *template)
1942 int pfd = mkstemp(template);
1943 chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
1945 p = fdopen(pfd, "w+");
1954 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)
1957 char host[MAXHOSTNAMELEN] = "";
1965 size_t len_passdata;
1966 char *greeting_attachment;
1974 gethostname(host, sizeof(host) - 1);
1976 if (strchr(srcemail, '@'))
1977 ast_copy_string(who, srcemail, sizeof(who));
1979 snprintf(who, sizeof(who), "%s@%s", srcemail, host);
1981 greeting_attachment = strrchr(ast_strdupa(attach), '/');
1982 if (greeting_attachment)
1983 *greeting_attachment++ = '\0';
1985 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
1986 ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
1987 fprintf(p, "Date: %s" ENDL, date);
1989 /* Set date format for voicemail mail */
1990 ast_strftime(date, sizeof(date), emaildateformat, &tm);
1992 if (!ast_strlen_zero(fromstring)) {
1993 struct ast_channel *ast;
1994 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
1996 int vmlen = strlen(fromstring) * 3 + 200;
1997 passdata = alloca(vmlen);
1998 memset(passdata, 0, vmlen);
1999 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2000 pbx_substitute_variables_helper(ast, fromstring, passdata, vmlen);
2001 len_passdata = strlen(passdata) * 2 + 3;
2002 passdata2 = alloca(len_passdata);
2003 fprintf(p, "From: %s <%s>" ENDL, quote(passdata, passdata2, len_passdata), who);
2004 ast_channel_free(ast);
2006 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2008 fprintf(p, "From: Asterisk PBX <%s>" ENDL, who);
2009 len_passdata = strlen(vmu->fullname) * 2 + 3;
2010 passdata2 = alloca(len_passdata);
2011 fprintf(p, "To: %s <%s>" ENDL, quote(vmu->fullname, passdata2, len_passdata), vmu->email);
2012 if (!ast_strlen_zero(emailsubject)) {
2013 struct ast_channel *ast;
2014 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2016 int vmlen = strlen(emailsubject) * 3 + 200;
2017 passdata = alloca(vmlen);
2018 memset(passdata, 0, vmlen);
2019 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2020 pbx_substitute_variables_helper(ast, emailsubject, passdata, vmlen);
2021 fprintf(p, "Subject: %s" ENDL, passdata);
2022 ast_channel_free(ast);
2024 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2025 } else if (!ast_strlen_zero(emailtitle)) {
2026 fprintf(p, emailtitle, msgnum + 1, mailbox) ;
2028 } else if (ast_test_flag((&globalflags), VM_PBXSKIP))
2029 fprintf(p, "Subject: New message %d in mailbox %s" ENDL, msgnum + 1, mailbox);
2031 fprintf(p, "Subject: [PBX]: New message %d in mailbox %s" ENDL, msgnum + 1, mailbox);
2032 fprintf(p, "Message-ID: <Asterisk-%d-%d-%s-%d@%s>" ENDL, msgnum + 1, (unsigned int)ast_random(), mailbox, (int)getpid(), host);
2034 /* additional information needed for IMAP searching */
2035 fprintf(p, "X-Asterisk-VM-Message-Num: %d" ENDL, msgnum + 1);
2036 /* fprintf(p, "X-Asterisk-VM-Orig-Mailbox: %s" ENDL, ext); */
2037 fprintf(p, "X-Asterisk-VM-Server-Name: %s" ENDL, fromstring);
2038 fprintf(p, "X-Asterisk-VM-Context: %s" ENDL, context);
2039 fprintf(p, "X-Asterisk-VM-Extension: %s" ENDL, mailbox);
2040 fprintf(p, "X-Asterisk-VM-Priority: %d" ENDL, chan->priority);
2041 fprintf(p, "X-Asterisk-VM-Caller-channel: %s" ENDL, chan->name);
2042 fprintf(p, "X-Asterisk-VM-Caller-ID-Num: %s" ENDL, cidnum);
2043 fprintf(p, "X-Asterisk-VM-Caller-ID-Name: %s" ENDL, cidname);
2044 fprintf(p, "X-Asterisk-VM-Duration: %d" ENDL, duration);
2045 if (!ast_strlen_zero(category))
2046 fprintf(p, "X-Asterisk-VM-Category: %s" ENDL, category);
2047 fprintf(p, "X-Asterisk-VM-Message-Type: %s" ENDL, msgnum > -1 ? "Message" : greeting_attachment);
2048 fprintf(p, "X-Asterisk-VM-Orig-date: %s" ENDL, date);
2049 fprintf(p, "X-Asterisk-VM-Orig-time: %ld" ENDL, (long)time(NULL));
2051 if (!ast_strlen_zero(cidnum))
2052 fprintf(p, "X-Asterisk-CallerID: %s" ENDL, cidnum);
2053 if (!ast_strlen_zero(cidname))
2054 fprintf(p, "X-Asterisk-CallerIDName: %s" ENDL, cidname);
2055 fprintf(p, "MIME-Version: 1.0" ENDL);
2056 if (attach_user_voicemail) {
2057 /* Something unique. */
2058 snprintf(bound, sizeof(bound), "----voicemail_%d%s%d%d", msgnum + 1, mailbox, (int)getpid(), (unsigned int)ast_random());
2060 fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"" ENDL, bound);
2061 fprintf(p, ENDL ENDL "This is a multi-part message in MIME format." ENDL ENDL);
2062 fprintf(p, "--%s" ENDL, bound);
2064 fprintf(p, "Content-Type: text/plain; charset=%s" ENDL "Content-Transfer-Encoding: 8bit" ENDL ENDL, charset);
2066 struct ast_channel *ast;
2067 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2069 int vmlen = strlen(emailbody) * 3 + 200;
2070 passdata = alloca(vmlen);
2071 memset(passdata, 0, vmlen);
2072 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2073 pbx_substitute_variables_helper(ast, emailbody, passdata, vmlen);
2074 fprintf(p, "%s" ENDL, passdata);
2075 ast_channel_free(ast);
2077 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2078 } else if (msgnum > -1) {
2079 fprintf(p, "Dear %s:" ENDL ENDL "\tJust wanted to let you know you were just left a %s long message (number %d)" ENDL
2081 "in mailbox %s from %s, on %s so you might" ENDL
2082 "want to check it when you get a chance. Thanks!" ENDL ENDL "\t\t\t\t--Asterisk" ENDL ENDL, vmu->fullname,
2083 dur, msgnum + 1, mailbox, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
2085 fprintf(p, "This message is to let you know that your greeting was changed on %s." ENDL
2086 "Please do not delete this message, lest your greeting vanish with it." ENDL ENDL, date);
2088 if (attach_user_voicemail) {
2089 /* Eww. We want formats to tell us their own MIME type */
2090 char *ctype = (!strcasecmp(format, "ogg")) ? "application/" : "audio/x-";
2091 char tmpdir[256], newtmp[256];
2094 if (vmu->volgain < -.001 || vmu->volgain > .001) {
2095 create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, vmu->mailbox, "tmp");
2096 snprintf(newtmp, sizeof(newtmp), "%s/XXXXXX", tmpdir);
2097 tmpfd = mkstemp(newtmp);
2098 chmod(newtmp, VOICEMAIL_FILE_MODE & ~my_umask);
2099 ast_debug(3, "newtmp: %s\n", newtmp);
2101 snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, attach, format, newtmp, format);
2102 ast_safe_system(tmpcmd);
2104 ast_debug(3, "VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", attach, format, vmu->volgain, mailbox);
2107 fprintf(p, "--%s" ENDL, bound);
2109 fprintf(p, "Content-Type: %s%s; name=\"msg%04d.%s\"" ENDL, ctype, format, msgnum + 1, format);
2111 fprintf(p, "Content-Type: %s%s; name=\"%s.%s\"" ENDL, ctype, format, greeting_attachment, format);
2112 fprintf(p, "Content-Transfer-Encoding: base64" ENDL);
2113 fprintf(p, "Content-Description: Voicemail sound attachment." ENDL);
2115 fprintf(p, "Content-Disposition: attachment; filename=\"msg%04d.%s\"" ENDL ENDL, msgnum + 1, format);
2117 fprintf(p, "Content-Disposition: attachment; filename=\"%s.%s\"" ENDL ENDL, greeting_attachment, format);
2118 snprintf(fname, sizeof(fname), "%s.%s", attach, format);
2119 base_encode(fname, p);
2120 fprintf(p, ENDL "--%s--" ENDL "." ENDL, bound);
2130 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)
2133 char tmp[80] = "/tmp/astmail-XXXXXX";
2136 if (vmu && ast_strlen_zero(vmu->email)) {
2137 ast_log(LOG_WARNING, "E-mail address missing for mailbox [%s]. E-mail will not be sent.\n", vmu->mailbox);
2140 if (!strcmp(format, "wav49"))
2142 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));
2143 /* Make a temporary file instead of piping directly to sendmail, in case the mail
2145 if ((p = vm_mkftemp(tmp)) == NULL) {
2146 ast_log(LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
2149 make_email_file(p, srcemail, vmu, msgnum, context, mailbox, cidnum, cidname, attach, format, duration, attach_user_voicemail, chan, category, 0);
2151 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
2152 ast_safe_system(tmp2);
2153 ast_debug(1, "Sent mail to %s with command '%s'\n", vmu->email, mailcmd);
2158 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)
2161 char host[MAXHOSTNAMELEN] = "";
2164 char tmp[80] = "/tmp/astmail-XXXXXX";
2165 char tmp2[PATH_MAX];
2169 if ((p = vm_mkftemp(tmp)) == NULL) {
2170 ast_log(LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
2173 gethostname(host, sizeof(host) - 1);
2174 if (strchr(srcemail, '@'))
2175 ast_copy_string(who, srcemail, sizeof(who));
2177 snprintf(who, sizeof(who), "%s@%s", srcemail, host);
2178 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
2179 ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
2180 fprintf(p, "Date: %s\n", date);
2182 if (*pagerfromstring) {
2183 struct ast_channel *ast;
2184 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2186 int vmlen = strlen(fromstring) * 3 + 200;
2187 passdata = alloca(vmlen);
2188 memset(passdata, 0, vmlen);
2189 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2190 pbx_substitute_variables_helper(ast, pagerfromstring, passdata, vmlen);
2191 fprintf(p, "From: %s <%s>\n", passdata, who);
2192 ast_channel_free(ast);
2194 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2196 fprintf(p, "From: Asterisk PBX <%s>\n", who);
2197 fprintf(p, "To: %s\n", pager);
2199 struct ast_channel *ast;
2200 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2202 int vmlen = strlen(pagersubject) * 3 + 200;
2203 passdata = alloca(vmlen);
2204 memset(passdata, 0, vmlen);
2205 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2206 pbx_substitute_variables_helper(ast, pagersubject, passdata, vmlen);
2207 fprintf(p, "Subject: %s\n\n", passdata);
2208 ast_channel_free(ast);
2210 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2212 fprintf(p, "Subject: New VM\n\n");
2214 ast_strftime(date, sizeof(date), "%A, %B %d, %Y at %r", &tm);
2216 struct ast_channel *ast;
2217 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2219 int vmlen = strlen(pagerbody) * 3 + 200;
2220 passdata = alloca(vmlen);
2221 memset(passdata, 0, vmlen);
2222 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2223 pbx_substitute_variables_helper(ast, pagerbody, passdata, vmlen);
2224 fprintf(p, "%s\n", passdata);
2225 ast_channel_free(ast);
2227 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2229 fprintf(p, "New %s long msg in box %s\n"
2230 "from %s, on %s", dur, mailbox, (cidname ? cidname : (cidnum ? cidnum : "unknown")), date);
2233 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
2234 ast_safe_system(tmp2);
2235 ast_debug(1, "Sent page to %s with command '%s'\n", pager, mailcmd);
2239 static int get_date(char *s, int len)
2242 struct timeval t = ast_tvnow();
2244 ast_localtime(&t, &tm, "UTC");
2246 return ast_strftime(s, len, "%a %b %e %r UTC %Y", &tm);
2249 static int play_greeting(struct ast_channel *chan, struct ast_vm_user *vmu, char *filename, char *ecodes)
2256 RETRIEVE(filename, -1, vmu->mailbox, vmu->context);
2257 if (ast_fileexists(filename, NULL, NULL) > 0) {
2258 res = ast_streamfile(chan, filename, chan->language);
2260 res = ast_waitstream(chan, ecodes);
2262 if (success == -1) {
2263 /* We couldn't retrieve the file from the database, but we found it on the file system. Let's put it in the database. */
2264 ast_debug(1, "Greeting not retrieved from database, but found in file storage. Inserting into database\n");
2265 store_file(filename, vmu->mailbox, vmu->context, -1);
2269 DISPOSE(filename, -1);
2274 static int invent_message(struct ast_channel *chan, struct ast_vm_user *vmu, char *ext, int busy, char *ecodes)
2278 char dest[PATH_MAX];
2280 snprintf(fn, sizeof(fn), "%s%s/%s/greet", VM_SPOOL_DIR, vmu->context, ext);
2282 if ((res = create_dirpath(dest, sizeof(dest), vmu->context, ext, ""))) {
2283 ast_log(LOG_WARNING, "Failed to make directory(%s)\n", fn);
2287 res = play_greeting(chan, vmu, fn, ecodes);
2289 /* File did not exist */
2290 res = ast_stream_and_wait(chan, "vm-theperson", ecodes);
2293 res = ast_say_digit_str(chan, ext, ecodes, chan->language);
2298 res = ast_stream_and_wait(chan, busy ? "vm-isonphone" : "vm-isunavail", ecodes);
2302 static void free_user(struct ast_vm_user *vmu)
2304 if (!ast_test_flag(vmu, VM_ALLOCED))
2310 static void free_zone(struct vm_zone *z)
2315 static const char *mbox(int id)
2317 static const char *msgs[] = {
2334 return (id >= 0 && id < (sizeof(msgs) / sizeof(msgs[0]))) ? msgs[id] : "tmp";
2337 static int folder_int(const char *folder)
2339 /*assume a NULL folder means INBOX*/
2343 if (!strcasecmp(folder, imapfolder))
2345 if (!strcasecmp(folder, "INBOX"))
2348 else if (!strcasecmp(folder, "Old"))
2350 else if (!strcasecmp(folder, "Work"))
2352 else if (!strcasecmp(folder, "Family"))
2354 else if (!strcasecmp(folder, "Friends"))
2356 else if (!strcasecmp(folder, "Cust1"))
2358 else if (!strcasecmp(folder, "Cust2"))
2360 else if (!strcasecmp(folder, "Cust3"))
2362 else if (!strcasecmp(folder, "Cust4"))
2364 else if (!strcasecmp(folder, "Cust5"))
2366 else if (!strcasecmp(folder, "Deleted"))
2368 else /*assume they meant INBOX if folder is not found otherwise*/
2374 /*! XXX \todo Fix this function to support multiple mailboxes in the intput string */
2375 static int inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
2382 char tmp[PATH_MAX] = "";
2383 struct odbc_obj *obj;
2385 struct generic_prepare_struct gps = { .sql = sql, .argc = 0 };
2392 /* If no mailbox, return immediately */
2393 if (ast_strlen_zero(mailbox))
2396 ast_copy_string(tmp, mailbox, sizeof(tmp));
2398 context = strchr(tmp, '@');
2403 context = "default";
2405 obj = ast_odbc_request_obj(odbc_database, 0);
2407 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "INBOX");
2408 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2410 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2411 ast_odbc_release_obj(obj);
2414 res = SQLFetch(stmt);
2415 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2416 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2417 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2418 ast_odbc_release_obj(obj);
2421 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2422 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2423 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2424 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2425 ast_odbc_release_obj(obj);
2428 *newmsgs = atoi(rowdata);
2429 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2431 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "Old");
2432 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2434 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2435 ast_odbc_release_obj(obj);
2438 res = SQLFetch(stmt);
2439 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2440 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2441 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2442 ast_odbc_release_obj(obj);
2445 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2446 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2447 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2448 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2449 ast_odbc_release_obj(obj);
2452 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2453 ast_odbc_release_obj(obj);
2454 *oldmsgs = atoi(rowdata);
2457 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2463 static int messagecount(const char *context, const char *mailbox, const char *folder)
2465 struct odbc_obj *obj = NULL;
2468 SQLHSTMT stmt = NULL;
2471 struct generic_prepare_struct gps = { .sql = sql, .argc = 0 };
2474 /* If no mailbox, return immediately */
2475 if (ast_strlen_zero(mailbox))
2478 obj = ast_odbc_request_obj(odbc_database, 0);
2480 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, mailbox, folder);
2481 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2483 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2486 res = SQLFetch(stmt);
2487 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2488 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2489 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2492 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2493 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2494 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2495 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2498 nummsgs = atoi(rowdata);
2499 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2501 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2505 ast_odbc_release_obj(obj);
2509 static int has_voicemail(const char *mailbox, const char *folder)
2511 char tmp[256], *tmp2 = tmp, *mbox, *context;
2512 ast_copy_string(tmp, mailbox, sizeof(tmp));
2513 while ((context = mbox = strsep(&tmp2, ","))) {
2514 strsep(&context, "@");
2515 if (ast_strlen_zero(context))
2516 context = "default";
2517 if (messagecount(context, mbox, folder))
2523 #elif defined(IMAP_STORAGE)
2525 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)
2527 char *myserveremail = serveremail;
2532 char tmp[80] = "/tmp/astmail-XXXXXX";
2538 /* Attach only the first format */
2539 fmt = ast_strdupa(fmt);
2541 strsep(&stringp, "|");
2543 if (!ast_strlen_zero(vmu->serveremail))
2544 myserveremail = vmu->serveremail;
2547 make_file(fn, sizeof(fn), dir, msgnum);
2549 ast_copy_string (fn, dir, sizeof(fn));
2551 if (ast_strlen_zero(vmu->email)) {
2552 /*we need the vmu->email to be set when we call make_email_file, but if we keep it set,
2553 * a duplicate e-mail will be created. So at the end of this function, we will revert back to an empty
2554 * string if tempcopy is 1
2556 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
2560 if (!strcmp(fmt, "wav49"))
2562 ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
2564 /* Make a temporary file instead of piping directly to sendmail, in case the mail
2566 if (!(p = vm_mkftemp(tmp))) {
2567 ast_log(LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
2569 *(vmu->email) = '\0';
2573 if (msgnum < 0 && imapgreetings) {
2574 init_mailstream(vms, GREETINGS_FOLDER);
2575 imap_delete_old_greeting(fn, vms);
2578 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);
2579 /* read mail file to memory */
2582 if (!(buf = ast_malloc(len + 1))) {
2583 ast_log(LOG_ERROR, "Can't allocate %ld bytes to read message\n", len + 1);
2586 *(vmu->email) = '\0';
2589 fread(buf, len, 1, p);
2590 ((char *)buf)[len] = '\0';
2591 INIT(&str, mail_string, buf, len);
2592 init_mailstream(vms, NEW_FOLDER);
2593 imap_mailbox_name(mailbox, sizeof(mailbox), vms, NEW_FOLDER, 1);
2594 if (!mail_append(vms->mailstream, mailbox, &str))
2595 ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
2599 ast_debug(3, "%s stored\n", fn);
2602 *(vmu->email) = '\0';
2608 static int messagecount(const char *context, const char *mailbox, const char *folder)
2613 struct ast_vm_user *vmu, vmus;
2614 struct vm_state *vms_p;
2616 int fold = folder_int(folder);
2618 if (ast_strlen_zero(mailbox))
2621 /* We have to get the user before we can open the stream! */
2622 /* ast_log(LOG_DEBUG, "Before find_user, context is %s and mailbox is %s\n", context, mailbox); */
2623 vmu = find_user(&vmus, context, mailbox);
2625 ast_log(LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailbox, context);
2628 /* No IMAP account available */
2629 if (vmu->imapuser[0] == '\0') {
2630 ast_log(LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
2635 /* No IMAP account available */
2636 if (vmu->imapuser[0] == '\0') {
2637 ast_log(LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
2642 /* check if someone is accessing this box right now... */
2643 vms_p = get_vm_state_by_imapuser(vmu->imapuser, 1);
2645 vms_p = get_vm_state_by_mailbox(mailbox, 1);
2648 ast_debug(3, "Returning before search - user is logged in\n");
2649 if (fold == 0) {/*INBOX*/
2650 return vms_p->newmessages;
2652 if (fold == 1) {/*Old messages*/
2653 return vms_p->oldmessages;
2657 /* add one if not there... */
2658 vms_p = get_vm_state_by_imapuser(vmu->imapuser, 0);
2660 vms_p = get_vm_state_by_mailbox(mailbox, 0);
2664 ast_debug(3, "Adding new vmstate for %s\n", vmu->imapuser);
2665 if (!(vms_p = ast_calloc(1, sizeof(*vms_p)))) {
2668 ast_copy_string(vms_p->imapuser, vmu->imapuser, sizeof(vms_p->imapuser));
2669 ast_copy_string(vms_p->username, mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
2670 vms_p->mailstream = NIL; /* save for access from interactive entry point */
2671 ast_debug(3, "Copied %s to %s\n", vmu->imapuser, vms_p->imapuser);
2673 /* set mailbox to INBOX! */
2674 ast_copy_string(vms_p->curbox, mbox(fold), sizeof(vms_p->curbox));
2675 init_vm_state(vms_p);
2676 vmstate_insert(vms_p);
2678 ret = init_mailstream(vms_p, fold);
2679 if (!vms_p->mailstream) {
2680 ast_log(LOG_ERROR, "Houston we have a problem - IMAP mailstream is NULL\n");
2684 pgm = mail_newsearchpgm ();
2685 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)mailbox);
2691 /* In the special case where fold is 1 (old messages) we have to do things a bit
2692 * differently. Old messages are stored in the INBOX but are marked as "seen"
2701 vms_p->vmArrayIndex = 0;
2702 mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
2704 vms_p->newmessages = vms_p->vmArrayIndex;
2706 vms_p->oldmessages = vms_p->vmArrayIndex;
2707 /*Freeing the searchpgm also frees the searchhdr*/
2708 mail_free_searchpgm(&pgm);
2710 return vms_p->vmArrayIndex;
2712 mail_ping(vms_p->mailstream);
2716 static int inboxcount(const char *mailbox_context, int *newmsgs, int *oldmsgs)
2718 char tmp[PATH_MAX] = "";
2728 ast_debug(3, "Mailbox is set to %s\n", mailbox_context);
2729 /* If no mailbox, return immediately */
2730 if (ast_strlen_zero(mailbox_context))
2733 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2734 context = strchr(tmp, '@');
2735 if (strchr(mailbox_context, ',')) {
2737 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2739 while ((cur = strsep(&mb, ", "))) {
2740 if (!ast_strlen_zero(cur)) {
2741 if (inboxcount(cur, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2758 context = "default";
2759 mailboxnc = (char *)mailbox_context;
2762 if ((*newmsgs = messagecount(context, mailboxnc, imapfolder)) < 0)
2766 if ((*oldmsgs = messagecount(context, mailboxnc, "Old")) < 0)
2773 static int has_voicemail(const char *mailbox, const char *folder)
2775 char tmp[256], *tmp2, *mbox, *context;
2776 ast_copy_string(tmp, mailbox, sizeof(tmp));
2778 if (strchr(tmp2, ',')) {
2779 while ((mbox = strsep(&tmp2, ","))) {
2780 if (!ast_strlen_zero(mbox)) {
2781 if (has_voicemail(mbox, folder))
2786 if ((context= strchr(tmp, '@')))
2789 context = "default";
2790 return messagecount(context, tmp, folder) ? 1 : 0;
2793 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)
2795 struct vm_state *sendvms = NULL, *destvms = NULL;
2796 char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
2797 if (!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 0))) {
2798 ast_log(LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
2801 if (!(destvms = get_vm_state_by_imapuser(recip->imapuser, 0))) {
2802 ast_log(LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
2805 snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
2806 if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(imbox)) == T))
2808 ast_log(LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
2813 #ifndef IMAP_STORAGE
2814 /* copy message only used by file storage */
2815 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)
2817 char fromdir[PATH_MAX], todir[PATH_MAX], frompath[PATH_MAX], topath[PATH_MAX];
2818 const char *frombox = mbox(imbox);
2821 ast_log(LOG_NOTICE, "Copying message from %s@%s to %s@%s\n", vmu->mailbox, vmu->context, recip->mailbox, recip->context);
2823 create_dirpath(todir, sizeof(todir), recip->context, recip->mailbox, "INBOX");
2826 make_dir(fromdir, sizeof(fromdir), vmu->context, vmu->mailbox, frombox);
2828 ast_copy_string(fromdir, dir, sizeof(fromdir));
2830 make_file(frompath, sizeof(frompath), fromdir, msgnum);
2831 make_dir(todir, sizeof(todir), recip->context, recip->mailbox, "INBOX");
2833 if (vm_lock_path(todir))
2834 return ERROR_LOCK_PATH;
2836 recipmsgnum = last_message_index(recip, todir) + 1;
2837 if (recipmsgnum < recip->maxmsg) {
2838 make_file(topath, sizeof(topath), todir, recipmsgnum);
2839 COPY(fromdir, msgnum, todir, recipmsgnum, recip->mailbox, recip->context, frompath, topath);
2841 ast_log(LOG_ERROR, "Recipient mailbox %s@%s is full\n", recip->mailbox, recip->context);
2843 ast_unlock_path(todir);
2844 notify_new_message(chan, recip, NULL, recipmsgnum, duration, fmt, S_OR(chan->cid.cid_num, NULL), S_OR(chan->cid.cid_name, NULL));
2849 #if !(defined(IMAP_STORAGE) || defined(ODBC_STORAGE))
2850 static int messagecount(const char *context, const char *mailbox, const char *folder)
2852 return __has_voicemail(context, mailbox, folder, 0);
2856 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit)
2863 /* If no mailbox, return immediately */
2864 if (ast_strlen_zero(mailbox))
2867 if (ast_strlen_zero(folder))
2869 if (ast_strlen_zero(context))
2870 context = "default";
2872 snprintf(fn, sizeof(fn), "%s%s/%s/%s", VM_SPOOL_DIR, context, mailbox, folder);
2874 if (!(dir = opendir(fn)))
2877 while ((de = readdir(dir))) {
2878 if (!strncasecmp(de->d_name, "msg", 3)) {
2882 } else if (!strncasecmp(de->d_name + 8, "txt", 3))
2893 static int has_voicemail(const char *mailbox, const char *folder)
2895 char tmp[256], *tmp2 = tmp, *mbox, *context;
2896 ast_copy_string(tmp, mailbox, sizeof(tmp));
2897 while ((mbox = strsep(&tmp2, ","))) {
2898 if ((context = strchr(mbox, '@')))
2901 context = "default";
2902 if (__has_voicemail(context, mbox, folder, 1))
2909 static int inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
2914 /* If no mailbox, return immediately */
2915 if (ast_strlen_zero(mailbox))
2923 if (strchr(mailbox, ',')) {
2927 ast_copy_string(tmp, mailbox, sizeof(tmp));
2929 while ((cur = strsep(&mb, ", "))) {
2930 if (!ast_strlen_zero(cur)) {
2931 if (inboxcount(cur, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2944 ast_copy_string(tmp, mailbox, sizeof(tmp));
2946 if ((context = strchr(tmp, '@')))
2949 context = "default";
2952 *newmsgs = __has_voicemail(context, tmp, "INBOX", 0);
2954 *oldmsgs = __has_voicemail(context, tmp, "Old", 0);
2961 static void run_externnotify(char *context, char *extension)
2963 char arguments[255];
2964 char ext_context[256] = "";
2965 int newvoicemails = 0, oldvoicemails = 0;
2966 struct ast_smdi_mwi_message *mwi_msg;
2968 if (!ast_strlen_zero(context))
2969 snprintf(ext_context, sizeof(ext_context), "%s@%s", extension, context);
2971 ast_copy_string(ext_context, extension, sizeof(ext_context));
2974 if (ast_app_has_voicemail(ext_context, NULL))
2975 ast_smdi_mwi_set(smdi_iface, extension);
2977 ast_smdi_mwi_unset(smdi_iface, extension);
2979 if ((mwi_msg = ast_smdi_mwi_message_wait_station(smdi_iface, SMDI_MWI_WAIT_TIMEOUT, extension))) {
2980 ast_log(LOG_ERROR, "Error executing SMDI MWI change for %s\n", extension);
2981 if (!strncmp(mwi_msg->cause, "INV", 3))
2982 ast_log(LOG_ERROR, "Invalid MWI extension: %s\n", mwi_msg->fwd_st);
2983 else if (!strncmp(mwi_msg->cause, "BLK", 3))
2984 ast_log(LOG_WARNING, "MWI light was already on or off for %s\n", mwi_msg->fwd_st);
2985 ast_log(LOG_WARNING, "The switch reported '%s'\n", mwi_msg->cause);
2986 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
2988 ast_debug(1, "Successfully executed SMDI MWI change for %s\n", extension);
2992 if (!ast_strlen_zero(externnotify)) {
2993 if (inboxcount(ext_context, &newvoicemails, &oldvoicemails)) {
2994 ast_log(LOG_ERROR, "Problem in calculating number of voicemail messages available for extension %s\n", extension);
2996 snprintf(arguments, sizeof(arguments), "%s %s %s %d&", externnotify, context, extension, newvoicemails);
2997 ast_debug(1, "Executing %s\n", arguments);
2998 ast_safe_system(arguments);
3003 struct leave_vm_options {
3005 signed char record_gain;
3009 static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_options *options)
3012 int newmsgs, oldmsgs;
3013 struct vm_state *vms = NULL;
3015 char txtfile[PATH_MAX], tmptxtfile[PATH_MAX];
3031 char dir[PATH_MAX], tmpdir[PATH_MAX];
3033 char prefile[PATH_MAX] = "";
3034 char tempfile[PATH_MAX] = "";
3035 char ext_context[256] = "";
3038 char ecodes[17] = "#";
3039 char tmp[1024] = "", *tmpptr;
3040 struct ast_vm_user *vmu;
3041 struct ast_vm_user svm;
3042 const char *category = NULL, *code, *alldtmf = "0123456789ABCD*#";
3044 ast_copy_string(tmp, ext, sizeof(tmp));
3046 if ((context = strchr(tmp, '@'))) {
3048 tmpptr = strchr(context, '&');
3050 tmpptr = strchr(ext, '&');
3056 category = pbx_builtin_getvar_helper(chan, "VM_CATEGORY");
3058 ast_debug(3, "Before find_user\n");
3059 if (!(vmu = find_user(&svm, context, ext))) {
3060 ast_log(LOG_WARNING, "No entry in voicemail config file for '%s'\n", ext);
3061 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
3064 /* Setup pre-file if appropriate */
3065 if (strcmp(vmu->context, "default"))
3066 snprintf(ext_context, sizeof(ext_context), "%s@%s", ext, vmu->context);
3068 ast_copy_string(ext_context, vmu->mailbox, sizeof(ext_context));
3069 if (ast_test_flag(options, OPT_BUSY_GREETING)) {
3070 snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, ext);
3071 } else if (ast_test_flag(options, OPT_UNAVAIL_GREETING)) {
3072 snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, ext);
3074 snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", VM_SPOOL_DIR, vmu->context, ext);
3075 if ((res = create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, ext, "tmp"))) {
3076 ast_log(LOG_WARNING, "Failed to make directory (%s)\n", tempfile);
3079 RETRIEVE(tempfile, -1, ext, context);
3080 if (ast_fileexists(tempfile, NULL, NULL) > 0)
3081 ast_copy_string(prefile, tempfile, sizeof(prefile));
3082 DISPOSE(tempfile, -1);
3083 /* It's easier just to try to make it than to check for its existence */
3084 create_dirpath(dir, sizeof(dir), vmu->context, ext, "INBOX");
3086 /* Check current or macro-calling context for special extensions */
3087 if (ast_test_flag(vmu, VM_OPERATOR)) {
3088 if (!ast_strlen_zero(vmu->exit)) {
3089 if (ast_exists_extension(chan, vmu->exit, "o", 1, chan->cid.cid_num)) {
3090 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
3093 } else if (ast_exists_extension(chan, chan->context, "o", 1, chan->cid.cid_num)) {
3094 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
3097 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num)) {