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
237 OPT_SILENT = (1 << 0),
238 OPT_BUSY_GREETING = (1 << 1),
239 OPT_UNAVAIL_GREETING = (1 << 2),
240 OPT_RECORDGAIN = (1 << 3),
241 OPT_PREPEND_MAILBOX = (1 << 4),
242 OPT_AUTOPLAY = (1 << 6),
243 OPT_DTMFEXIT = (1 << 7),
247 OPT_ARG_RECORDGAIN = 0,
248 OPT_ARG_PLAYFOLDER = 1,
249 OPT_ARG_DTMFEXIT = 2,
250 /* This *must* be the last value in this enum! */
251 OPT_ARG_ARRAY_SIZE = 3,
254 AST_APP_OPTIONS(vm_app_options, {
255 AST_APP_OPTION('s', OPT_SILENT),
256 AST_APP_OPTION('b', OPT_BUSY_GREETING),
257 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
258 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
259 AST_APP_OPTION_ARG('d', OPT_DTMFEXIT, OPT_ARG_DTMFEXIT),
260 AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
261 AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
264 static int load_config(int reload);
266 /*! \page vmlang Voicemail Language Syntaxes Supported
268 \par Syntaxes supported, not really language codes.
275 \arg \b pt - Portuguese
276 \arg \b pt_BR - Portuguese (Brazil)
278 \arg \b no - Norwegian
280 \arg \b tw - Chinese (Taiwan)
281 \arg \b ua - Ukrainian
283 German requires the following additional soundfile:
284 \arg \b 1F einE (feminine)
286 Spanish requires the following additional soundfile:
287 \arg \b 1M un (masculine)
289 Dutch, Portuguese & Spanish require the following additional soundfiles:
290 \arg \b vm-INBOXs singular of 'new'
291 \arg \b vm-Olds singular of 'old/heard/read'
294 \arg \b vm-INBOX nieuwe (nl)
295 \arg \b vm-Old oude (nl)
298 \arg \b vm-new-a 'new', feminine singular accusative
299 \arg \b vm-new-e 'new', feminine plural accusative
300 \arg \b vm-new-ych 'new', feminine plural genitive
301 \arg \b vm-old-a 'old', feminine singular accusative
302 \arg \b vm-old-e 'old', feminine plural accusative
303 \arg \b vm-old-ych 'old', feminine plural genitive
304 \arg \b digits/1-a 'one', not always same as 'digits/1'
305 \arg \b digits/2-ie 'two', not always same as 'digits/2'
308 \arg \b vm-nytt singular of 'new'
309 \arg \b vm-nya plural of 'new'
310 \arg \b vm-gammalt singular of 'old'
311 \arg \b vm-gamla plural of 'old'
312 \arg \b digits/ett 'one', not always same as 'digits/1'
315 \arg \b vm-ny singular of 'new'
316 \arg \b vm-nye plural of 'new'
317 \arg \b vm-gammel singular of 'old'
318 \arg \b vm-gamle plural of 'old'
326 Ukrainian requires the following additional soundfile:
327 \arg \b vm-nove 'nove'
328 \arg \b vm-stare 'stare'
329 \arg \b digits/ua/1e 'odne'
331 Italian requires the following additional soundfile:
335 \arg \b vm-nuovi new plural
336 \arg \b vm-vecchio old
337 \arg \b vm-vecchi old plural
339 Chinese (Taiwan) requires the following additional soundfile:
340 \arg \b vm-tong A class-word for call (tong1)
341 \arg \b vm-ri A class-word for day (ri4)
342 \arg \b vm-you You (ni3)
343 \arg \b vm-haveno Have no (mei2 you3)
344 \arg \b vm-have Have (you3)
345 \arg \b vm-listen To listen (yao4 ting1)
348 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
349 spelled among others when you have to change folder. For the above reasons, vm-INBOX
350 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
359 unsigned char iobuf[BASEMAXINLINE];
362 /*! Structure for linked list of users
363 * Use ast_vm_user_destroy() to free one of these structures. */
365 char context[AST_MAX_CONTEXT]; /*!< Voicemail context */
366 char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
367 char password[80]; /*!< Secret pin code, numbers only */
368 char fullname[80]; /*!< Full name, for directory app */
369 char email[80]; /*!< E-mail address */
370 char pager[80]; /*!< E-mail address to pager (no attachment) */
371 char serveremail[80]; /*!< From: Mail address */
372 char mailcmd[160]; /*!< Configurable mail command */
373 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
374 char zonetag[80]; /*!< Time zone */
377 char uniqueid[20]; /*!< Unique integer identifier */
379 char attachfmt[20]; /*!< Attachment format */
380 unsigned int flags; /*!< VM_ flags */
382 int maxmsg; /*!< Maximum number of msgs per folder for this mailbox */
383 int maxdeletedmsg; /*!< Maximum number of deleted msgs saved for this mailbox */
384 int maxsecs; /*!< Maximum number of seconds per message for this mailbox */
386 char imapuser[80]; /*!< IMAP server login */
387 char imappassword[80]; /*!< IMAP server password if authpassword not defined */
389 double volgain; /*!< Volume gain for voicemails sent via email */
390 AST_LIST_ENTRY(ast_vm_user) list;
393 /*! Voicemail time zones */
395 AST_LIST_ENTRY(vm_zone) list;
398 char msg_format[512];
401 /*! Voicemail mailbox state */
405 char curdir[PATH_MAX];
406 char vmbox[PATH_MAX];
418 int updated; /*!< decremented on each mail check until 1 -allows delay */
420 MAILSTREAM *mailstream;
422 char imapuser[80]; /*!< IMAP server login */
424 unsigned int quota_limit;
425 unsigned int quota_usage;
426 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_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, 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);
1628 static int copy(char *infile, char *outfile)
1636 #ifdef HARDLINK_WHEN_POSSIBLE
1637 /* Hard link if possible; saves disk space & is faster */
1638 if (link(infile, outfile)) {
1640 if ((ifd = open(infile, O_RDONLY)) < 0) {
1641 ast_log(LOG_WARNING, "Unable to open %s in read-only mode\n", infile);
1644 if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, VOICEMAIL_FILE_MODE)) < 0) {
1645 ast_log(LOG_WARNING, "Unable to open %s in write-only mode\n", outfile);
1650 len = read(ifd, buf, sizeof(buf));
1652 ast_log(LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
1658 res = write(ofd, buf, len);
1659 if (errno == ENOMEM || errno == ENOSPC || res != len) {
1660 ast_log(LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno));
1670 #ifdef HARDLINK_WHEN_POSSIBLE
1672 /* Hard link succeeded */
1678 static void copy_file(char *frompath, char *topath)
1680 char frompath2[PATH_MAX], topath2[PATH_MAX];
1681 struct ast_variable *tmp, *var = NULL;
1682 const char *origmailbox = NULL, *context = NULL, *macrocontext = NULL, *exten = NULL, *priority = NULL, *callerchan = NULL, *callerid = NULL, *origdate = NULL, *origtime = NULL, *category = NULL, *duration = NULL;
1683 ast_filecopy(frompath, topath, NULL);
1684 snprintf(frompath2, sizeof(frompath2), "%s.txt", frompath);
1685 snprintf(topath2, sizeof(topath2), "%s.txt", topath);
1686 if (ast_check_realtime("voicemail_data")) {
1687 var = ast_load_realtime("voicemail_data", "filename", frompath, NULL);
1688 /* This cycle converts ast_variable linked list, to va_list list of arguments, may be there is a better way to do it? */
1689 for (tmp = var; tmp; tmp = tmp->next) {
1690 if (!strcasecmp(tmp->name, "origmailbox")) {
1691 origmailbox = tmp->value;
1692 } else if (!strcasecmp(tmp->name, "context")) {
1693 context = tmp->value;
1694 } else if (!strcasecmp(tmp->name, "macrocontext")) {
1695 macrocontext = tmp->value;
1696 } else if (!strcasecmp(tmp->name, "exten")) {
1698 } else if (!strcasecmp(tmp->name, "priority")) {
1699 priority = tmp->value;
1700 } else if (!strcasecmp(tmp->name, "callerchan")) {
1701 callerchan = tmp->value;
1702 } else if (!strcasecmp(tmp->name, "callerid")) {
1703 callerid = tmp->value;
1704 } else if (!strcasecmp(tmp->name, "origdate")) {
1705 origdate = tmp->value;
1706 } else if (!strcasecmp(tmp->name, "origtime")) {
1707 origtime = tmp->value;
1708 } else if (!strcasecmp(tmp->name, "category")) {
1709 category = tmp->value;
1710 } else if (!strcasecmp(tmp->name, "duration")) {
1711 duration = tmp->value;
1714 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);
1716 copy(frompath2, topath2);
1717 ast_variables_destroy(var);
1720 * A negative return value indicates an error.
1721 * \note Should always be called with a lock already set on dir.
1723 static int last_message_index(struct ast_vm_user *vmu, char *dir)
1726 unsigned char map[MAXMSGLIMIT] = "";
1728 struct dirent *msgdirent;
1731 /* Reading the entire directory into a file map scales better than
1732 * doing a stat repeatedly on a predicted sequence. I suspect this
1733 * is partially due to stat(2) internally doing a readdir(2) itself to
1734 * find each file. */
1735 msgdir = opendir(dir);
1736 while ((msgdirent = readdir(msgdir))) {
1737 if (sscanf(msgdirent->d_name, "msg%d", &msgdirint) == 1 && msgdirint < MAXMSGLIMIT)
1742 for (x = 0; x < vmu->maxmsg; x++) {
1750 #endif /*#ifndef IMAP_STORAGE*/
1751 #endif /*#else of #ifdef ODBC_STORAGE*/
1752 #ifndef ODBC_STORAGE
1753 static int vm_delete(char *file)
1758 txtsize = (strlen(file) + 5) * sizeof(char);
1759 txt = alloca(txtsize);
1760 /* Sprintf here would safe because we alloca'd exactly the right length,
1761 * but trying to eliminate all sprintf's anyhow
1763 if (ast_check_realtime("voicemail_data")) {
1764 ast_destroy_realtime("voicemail_data", "filename", file, NULL);
1766 snprintf(txt, txtsize, "%s.txt", file);
1768 return ast_filedelete(file, NULL);
1772 static int inbuf(struct baseio *bio, FILE *fi)
1779 if ((l = fread(bio->iobuf, 1, BASEMAXINLINE, fi)) <= 0) {
1793 static int inchar(struct baseio *bio, FILE *fi)
1795 if (bio->iocp >= bio->iolen) {
1796 if (!inbuf(bio, fi))
1800 return bio->iobuf[bio->iocp++];
1803 static int ochar(struct baseio *bio, int c, FILE *so)
1805 if (bio->linelength >= BASELINELEN) {
1806 if (fputs(eol, so) == EOF)
1812 if (putc(((unsigned char)c), so) == EOF)
1820 static int base_encode(char *filename, FILE *so)
1822 static const unsigned char dtable[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
1823 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
1824 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0',
1825 '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
1830 memset(&bio, 0, sizeof(bio));
1831 bio.iocp = BASEMAXINLINE;
1833 if (!(fi = fopen(filename, "rb"))) {
1834 ast_log(LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno));
1839 unsigned char igroup[3], ogroup[4];
1842 igroup[0] = igroup[1] = igroup[2] = 0;
1844 for (n = 0; n < 3; n++) {
1845 if ((c = inchar(&bio, fi)) == EOF) {
1850 igroup[n] = (unsigned char)c;
1854 ogroup[0] = dtable[igroup[0] >> 2];
1855 ogroup[1] = dtable[((igroup[0] & 3) << 4) | (igroup[1] >> 4)];
1856 ogroup[2] = dtable[((igroup[1] & 0xF) << 2) | (igroup[2] >> 6)];
1857 ogroup[3] = dtable[igroup[2] & 0x3F];
1866 for (i = 0; i < 4; i++)
1867 ochar(&bio, ogroup[i], so);
1873 if (fputs(eol, so) == EOF)
1879 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)
1882 /* Prepare variables for substitution in email body and subject */
1883 pbx_builtin_setvar_helper(ast, "VM_NAME", vmu->fullname);
1884 pbx_builtin_setvar_helper(ast, "VM_DUR", dur);
1885 snprintf(passdata, passdatasize, "%d", msgnum);
1886 pbx_builtin_setvar_helper(ast, "VM_MSGNUM", passdata);
1887 pbx_builtin_setvar_helper(ast, "VM_CONTEXT", context);
1888 pbx_builtin_setvar_helper(ast, "VM_MAILBOX", mailbox);
1889 pbx_builtin_setvar_helper(ast, "VM_CALLERID", ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, "Unknown Caller"));
1890 pbx_builtin_setvar_helper(ast, "VM_CIDNAME", (cidname ? cidname : "an unknown caller"));
1891 pbx_builtin_setvar_helper(ast, "VM_CIDNUM", (cidnum ? cidnum : "an unknown caller"));
1892 pbx_builtin_setvar_helper(ast, "VM_DATE", date);
1893 pbx_builtin_setvar_helper(ast, "VM_CATEGORY", category ? ast_strdupa(category) : "no category");
1896 static char *quote(const char *from, char *to, size_t len)
1900 for (; ptr < to + len - 1; from++) {
1903 else if (*from == '\0')
1907 if (ptr < to + len - 1)
1914 * fill in *tm for current time according to the proper timezone, if any.
1915 * Return tm so it can be used as a function argument.
1917 static const struct ast_tm *vmu_tm(const struct ast_vm_user *vmu, struct ast_tm *tm)
1919 const struct vm_zone *z = NULL;
1920 struct timeval t = ast_tvnow();
1922 /* Does this user have a timezone specified? */
1923 if (!ast_strlen_zero(vmu->zonetag)) {
1924 /* Find the zone in the list */
1925 AST_LIST_LOCK(&zones);
1926 AST_LIST_TRAVERSE(&zones, z, list) {
1927 if (!strcmp(z->name, vmu->zonetag))
1930 AST_LIST_UNLOCK(&zones);
1932 ast_localtime(&t, tm, z ? z->timezone : NULL);
1936 /*! \brief same as mkstemp, but return a FILE * */
1937 static FILE *vm_mkftemp(char *template)
1940 int pfd = mkstemp(template);
1941 chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
1943 p = fdopen(pfd, "w+");
1952 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)
1955 char host[MAXHOSTNAMELEN] = "";
1963 size_t len_passdata;
1964 char *greeting_attachment;
1972 gethostname(host, sizeof(host) - 1);
1974 if (strchr(srcemail, '@'))
1975 ast_copy_string(who, srcemail, sizeof(who));
1977 snprintf(who, sizeof(who), "%s@%s", srcemail, host);
1979 greeting_attachment = strrchr(ast_strdupa(attach), '/');
1980 if (greeting_attachment)
1981 *greeting_attachment++ = '\0';
1983 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
1984 ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
1985 fprintf(p, "Date: %s" ENDL, date);
1987 /* Set date format for voicemail mail */
1988 ast_strftime(date, sizeof(date), emaildateformat, &tm);
1990 if (!ast_strlen_zero(fromstring)) {
1991 struct ast_channel *ast;
1992 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
1994 int vmlen = strlen(fromstring) * 3 + 200;
1995 passdata = alloca(vmlen);
1996 memset(passdata, 0, vmlen);
1997 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
1998 pbx_substitute_variables_helper(ast, fromstring, passdata, vmlen);
1999 len_passdata = strlen(passdata) * 2 + 3;
2000 passdata2 = alloca(len_passdata);
2001 fprintf(p, "From: %s <%s>" ENDL, quote(passdata, passdata2, len_passdata), who);
2002 ast_channel_free(ast);
2004 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2006 fprintf(p, "From: Asterisk PBX <%s>" ENDL, who);
2007 len_passdata = strlen(vmu->fullname) * 2 + 3;
2008 passdata2 = alloca(len_passdata);
2009 fprintf(p, "To: %s <%s>" ENDL, quote(vmu->fullname, passdata2, len_passdata), vmu->email);
2010 if (!ast_strlen_zero(emailsubject)) {
2011 struct ast_channel *ast;
2012 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2014 int vmlen = strlen(emailsubject) * 3 + 200;
2015 passdata = alloca(vmlen);
2016 memset(passdata, 0, vmlen);
2017 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2018 pbx_substitute_variables_helper(ast, emailsubject, passdata, vmlen);
2019 fprintf(p, "Subject: %s" ENDL, passdata);
2020 ast_channel_free(ast);
2022 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2023 } else if (!ast_strlen_zero(emailtitle)) {
2024 fprintf(p, emailtitle, msgnum + 1, mailbox) ;
2026 } else if (ast_test_flag((&globalflags), VM_PBXSKIP))
2027 fprintf(p, "Subject: New message %d in mailbox %s" ENDL, msgnum + 1, mailbox);
2029 fprintf(p, "Subject: [PBX]: New message %d in mailbox %s" ENDL, msgnum + 1, mailbox);
2030 fprintf(p, "Message-ID: <Asterisk-%d-%d-%s-%d@%s>" ENDL, msgnum + 1, (unsigned int)ast_random(), mailbox, (int)getpid(), host);
2032 /* additional information needed for IMAP searching */
2033 fprintf(p, "X-Asterisk-VM-Message-Num: %d" ENDL, msgnum + 1);
2034 /* fprintf(p, "X-Asterisk-VM-Orig-Mailbox: %s" ENDL, ext); */
2035 fprintf(p, "X-Asterisk-VM-Server-Name: %s" ENDL, fromstring);
2036 fprintf(p, "X-Asterisk-VM-Context: %s" ENDL, context);
2037 fprintf(p, "X-Asterisk-VM-Extension: %s" ENDL, mailbox);
2038 fprintf(p, "X-Asterisk-VM-Priority: %d" ENDL, chan->priority);
2039 fprintf(p, "X-Asterisk-VM-Caller-channel: %s" ENDL, chan->name);
2040 fprintf(p, "X-Asterisk-VM-Caller-ID-Num: %s" ENDL, cidnum);
2041 fprintf(p, "X-Asterisk-VM-Caller-ID-Name: %s" ENDL, cidname);
2042 fprintf(p, "X-Asterisk-VM-Duration: %d" ENDL, duration);
2043 if (!ast_strlen_zero(category))
2044 fprintf(p, "X-Asterisk-VM-Category: %s" ENDL, category);
2045 fprintf(p, "X-Asterisk-VM-Message-Type: %s" ENDL, msgnum > -1 ? "Message" : greeting_attachment);
2046 fprintf(p, "X-Asterisk-VM-Orig-date: %s" ENDL, date);
2047 fprintf(p, "X-Asterisk-VM-Orig-time: %ld" ENDL, (long)time(NULL));
2049 if (!ast_strlen_zero(cidnum))
2050 fprintf(p, "X-Asterisk-CallerID: %s" ENDL, cidnum);
2051 if (!ast_strlen_zero(cidname))
2052 fprintf(p, "X-Asterisk-CallerIDName: %s" ENDL, cidname);
2053 fprintf(p, "MIME-Version: 1.0" ENDL);
2054 if (attach_user_voicemail) {
2055 /* Something unique. */
2056 snprintf(bound, sizeof(bound), "----voicemail_%d%s%d%d", msgnum + 1, mailbox, (int)getpid(), (unsigned int)ast_random());
2058 fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"" ENDL, bound);
2059 fprintf(p, ENDL ENDL "This is a multi-part message in MIME format." ENDL ENDL);
2060 fprintf(p, "--%s" ENDL, bound);
2062 fprintf(p, "Content-Type: text/plain; charset=%s" ENDL "Content-Transfer-Encoding: 8bit" ENDL ENDL, charset);
2064 struct ast_channel *ast;
2065 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2067 int vmlen = strlen(emailbody) * 3 + 200;
2068 passdata = alloca(vmlen);
2069 memset(passdata, 0, vmlen);
2070 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2071 pbx_substitute_variables_helper(ast, emailbody, passdata, vmlen);
2072 fprintf(p, "%s" ENDL, passdata);
2073 ast_channel_free(ast);
2075 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2076 } else if (msgnum > -1) {
2077 fprintf(p, "Dear %s:" ENDL ENDL "\tJust wanted to let you know you were just left a %s long message (number %d)" ENDL
2079 "in mailbox %s from %s, on %s so you might" ENDL
2080 "want to check it when you get a chance. Thanks!" ENDL ENDL "\t\t\t\t--Asterisk" ENDL ENDL, vmu->fullname,
2081 dur, msgnum + 1, mailbox, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
2083 fprintf(p, "This message is to let you know that your greeting was changed on %s." ENDL
2084 "Please do not delete this message, lest your greeting vanish with it." ENDL ENDL, date);
2086 if (attach_user_voicemail) {
2087 /* Eww. We want formats to tell us their own MIME type */
2088 char *ctype = (!strcasecmp(format, "ogg")) ? "application/" : "audio/x-";
2089 char tmpdir[256], newtmp[256];
2092 if (vmu->volgain < -.001 || vmu->volgain > .001) {
2093 create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, vmu->mailbox, "tmp");
2094 snprintf(newtmp, sizeof(newtmp), "%s/XXXXXX", tmpdir);
2095 tmpfd = mkstemp(newtmp);
2096 chmod(newtmp, VOICEMAIL_FILE_MODE & ~my_umask);
2097 ast_debug(3, "newtmp: %s\n", newtmp);
2099 snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, attach, format, newtmp, format);
2100 ast_safe_system(tmpcmd);
2102 ast_debug(3, "VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", attach, format, vmu->volgain, mailbox);
2105 fprintf(p, "--%s" ENDL, bound);
2107 fprintf(p, "Content-Type: %s%s; name=\"msg%04d.%s\"" ENDL, ctype, format, msgnum + 1, format);
2109 fprintf(p, "Content-Type: %s%s; name=\"%s.%s\"" ENDL, ctype, format, greeting_attachment, format);
2110 fprintf(p, "Content-Transfer-Encoding: base64" ENDL);
2111 fprintf(p, "Content-Description: Voicemail sound attachment." ENDL);
2113 fprintf(p, "Content-Disposition: attachment; filename=\"msg%04d.%s\"" ENDL ENDL, msgnum + 1, format);
2115 fprintf(p, "Content-Disposition: attachment; filename=\"%s.%s\"" ENDL ENDL, greeting_attachment, format);
2116 snprintf(fname, sizeof(fname), "%s.%s", attach, format);
2117 base_encode(fname, p);
2118 fprintf(p, ENDL "--%s--" ENDL "." ENDL, bound);
2128 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)
2131 char tmp[80] = "/tmp/astmail-XXXXXX";
2134 if (vmu && ast_strlen_zero(vmu->email)) {
2135 ast_log(LOG_WARNING, "E-mail address missing for mailbox [%s]. E-mail will not be sent.\n", vmu->mailbox);
2138 if (!strcmp(format, "wav49"))
2140 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));
2141 /* Make a temporary file instead of piping directly to sendmail, in case the mail
2143 if ((p = vm_mkftemp(tmp)) == NULL) {
2144 ast_log(LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
2147 make_email_file(p, srcemail, vmu, msgnum, context, mailbox, cidnum, cidname, attach, format, duration, attach_user_voicemail, chan, category, 0);
2149 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
2150 ast_safe_system(tmp2);
2151 ast_debug(1, "Sent mail to %s with command '%s'\n", vmu->email, mailcmd);
2156 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)
2159 char host[MAXHOSTNAMELEN] = "";
2162 char tmp[80] = "/tmp/astmail-XXXXXX";
2163 char tmp2[PATH_MAX];
2167 if ((p = vm_mkftemp(tmp)) == NULL) {
2168 ast_log(LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
2171 gethostname(host, sizeof(host) - 1);
2172 if (strchr(srcemail, '@'))
2173 ast_copy_string(who, srcemail, sizeof(who));
2175 snprintf(who, sizeof(who), "%s@%s", srcemail, host);
2176 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
2177 ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
2178 fprintf(p, "Date: %s\n", date);
2180 if (*pagerfromstring) {
2181 struct ast_channel *ast;
2182 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2184 int vmlen = strlen(fromstring) * 3 + 200;
2185 passdata = alloca(vmlen);
2186 memset(passdata, 0, vmlen);
2187 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2188 pbx_substitute_variables_helper(ast, pagerfromstring, passdata, vmlen);
2189 fprintf(p, "From: %s <%s>\n", passdata, who);
2190 ast_channel_free(ast);
2192 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2194 fprintf(p, "From: Asterisk PBX <%s>\n", who);
2195 fprintf(p, "To: %s\n", pager);
2197 struct ast_channel *ast;
2198 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2200 int vmlen = strlen(pagersubject) * 3 + 200;
2201 passdata = alloca(vmlen);
2202 memset(passdata, 0, vmlen);
2203 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2204 pbx_substitute_variables_helper(ast, pagersubject, passdata, vmlen);
2205 fprintf(p, "Subject: %s\n\n", passdata);
2206 ast_channel_free(ast);
2208 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2210 fprintf(p, "Subject: New VM\n\n");
2212 ast_strftime(date, sizeof(date), "%A, %B %d, %Y at %r", &tm);
2214 struct ast_channel *ast;
2215 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2217 int vmlen = strlen(pagerbody) * 3 + 200;
2218 passdata = alloca(vmlen);
2219 memset(passdata, 0, vmlen);
2220 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2221 pbx_substitute_variables_helper(ast, pagerbody, passdata, vmlen);
2222 fprintf(p, "%s\n", passdata);
2223 ast_channel_free(ast);
2225 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2227 fprintf(p, "New %s long msg in box %s\n"
2228 "from %s, on %s", dur, mailbox, (cidname ? cidname : (cidnum ? cidnum : "unknown")), date);
2231 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
2232 ast_safe_system(tmp2);
2233 ast_debug(1, "Sent page to %s with command '%s'\n", pager, mailcmd);
2237 static int get_date(char *s, int len)
2240 struct timeval t = ast_tvnow();
2242 ast_localtime(&t, &tm, "UTC");
2244 return ast_strftime(s, len, "%a %b %e %r UTC %Y", &tm);
2247 static int play_greeting(struct ast_channel *chan, struct ast_vm_user *vmu, char *filename, char *ecodes)
2254 RETRIEVE(filename, -1, vmu->mailbox, vmu->context);
2255 if (ast_fileexists(filename, NULL, NULL) > 0) {
2256 res = ast_streamfile(chan, filename, chan->language);
2258 res = ast_waitstream(chan, ecodes);
2260 if (success == -1) {
2261 /* We couldn't retrieve the file from the database, but we found it on the file system. Let's put it in the database. */
2262 ast_debug(1, "Greeting not retrieved from database, but found in file storage. Inserting into database\n");
2263 store_file(filename, vmu->mailbox, vmu->context, -1);
2267 DISPOSE(filename, -1);
2272 static int invent_message(struct ast_channel *chan, struct ast_vm_user *vmu, char *ext, int busy, char *ecodes)
2276 char dest[PATH_MAX];
2278 snprintf(fn, sizeof(fn), "%s%s/%s/greet", VM_SPOOL_DIR, vmu->context, ext);
2280 if ((res = create_dirpath(dest, sizeof(dest), vmu->context, ext, ""))) {
2281 ast_log(LOG_WARNING, "Failed to make directory(%s)\n", fn);
2285 res = play_greeting(chan, vmu, fn, ecodes);
2287 /* File did not exist */
2288 res = ast_stream_and_wait(chan, "vm-theperson", ecodes);
2291 res = ast_say_digit_str(chan, ext, ecodes, chan->language);
2296 res = ast_stream_and_wait(chan, busy ? "vm-isonphone" : "vm-isunavail", ecodes);
2300 static void free_user(struct ast_vm_user *vmu)
2302 if (!ast_test_flag(vmu, VM_ALLOCED))
2308 static void free_zone(struct vm_zone *z)
2313 static const char *mbox(int id)
2315 static const char *msgs[] = {
2332 return (id >= 0 && id < (sizeof(msgs) / sizeof(msgs[0]))) ? msgs[id] : "Unknown";
2335 static int folder_int(const char *folder)
2337 /*assume a NULL folder means INBOX*/
2341 if (!strcasecmp(folder, imapfolder))
2343 if (!strcasecmp(folder, "INBOX"))
2346 else if (!strcasecmp(folder, "Old"))
2348 else if (!strcasecmp(folder, "Work"))
2350 else if (!strcasecmp(folder, "Family"))
2352 else if (!strcasecmp(folder, "Friends"))
2354 else if (!strcasecmp(folder, "Cust1"))
2356 else if (!strcasecmp(folder, "Cust2"))
2358 else if (!strcasecmp(folder, "Cust3"))
2360 else if (!strcasecmp(folder, "Cust4"))
2362 else if (!strcasecmp(folder, "Cust5"))
2364 else if (!strcasecmp(folder, "Deleted"))
2366 else /*assume they meant INBOX if folder is not found otherwise*/
2372 /*! XXX \todo Fix this function to support multiple mailboxes in the intput string */
2373 static int inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
2380 char tmp[PATH_MAX] = "";
2381 struct odbc_obj *obj;
2383 struct generic_prepare_struct gps = { .sql = sql, .argc = 0 };
2390 /* If no mailbox, return immediately */
2391 if (ast_strlen_zero(mailbox))
2394 ast_copy_string(tmp, mailbox, sizeof(tmp));
2396 context = strchr(tmp, '@');
2401 context = "default";
2403 obj = ast_odbc_request_obj(odbc_database, 0);
2405 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "INBOX");
2406 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2408 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2409 ast_odbc_release_obj(obj);
2412 res = SQLFetch(stmt);
2413 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2414 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2415 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2416 ast_odbc_release_obj(obj);
2419 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2420 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2421 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2422 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2423 ast_odbc_release_obj(obj);
2426 *newmsgs = atoi(rowdata);
2427 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2429 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "Old");
2430 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2432 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2433 ast_odbc_release_obj(obj);
2436 res = SQLFetch(stmt);
2437 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2438 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2439 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2440 ast_odbc_release_obj(obj);
2443 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2444 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2445 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2446 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2447 ast_odbc_release_obj(obj);
2450 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2451 ast_odbc_release_obj(obj);
2452 *oldmsgs = atoi(rowdata);
2455 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2461 static int messagecount(const char *context, const char *mailbox, const char *folder)
2463 struct odbc_obj *obj = NULL;
2466 SQLHSTMT stmt = NULL;
2469 struct generic_prepare_struct gps = { .sql = sql, .argc = 0 };
2472 /* If no mailbox, return immediately */
2473 if (ast_strlen_zero(mailbox))
2476 obj = ast_odbc_request_obj(odbc_database, 0);
2478 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, mailbox, folder);
2479 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2481 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2484 res = SQLFetch(stmt);
2485 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2486 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2487 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2490 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2491 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2492 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2493 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2496 nummsgs = atoi(rowdata);
2497 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2499 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2503 ast_odbc_release_obj(obj);
2507 static int has_voicemail(const char *mailbox, const char *folder)
2509 char tmp[256], *tmp2 = tmp, *mbox, *context;
2510 ast_copy_string(tmp, mailbox, sizeof(tmp));
2511 while ((context = mbox = strsep(&tmp2, ","))) {
2512 strsep(&context, "@");
2513 if (ast_strlen_zero(context))
2514 context = "default";
2515 if (messagecount(context, mbox, folder))
2521 #elif defined(IMAP_STORAGE)
2523 static int imap_store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms)
2525 char *myserveremail = serveremail;
2530 char tmp[80] = "/tmp/astmail-XXXXXX";
2536 /* Attach only the first format */
2537 fmt = ast_strdupa(fmt);
2539 strsep(&stringp, "|");
2541 if (!ast_strlen_zero(vmu->serveremail))
2542 myserveremail = vmu->serveremail;
2545 make_file(fn, sizeof(fn), dir, msgnum);
2547 ast_copy_string (fn, dir, sizeof(fn));
2549 if (ast_strlen_zero(vmu->email)) {
2550 /*we need the vmu->email to be set when we call make_email_file, but if we keep it set,
2551 * a duplicate e-mail will be created. So at the end of this function, we will revert back to an empty
2552 * string if tempcopy is 1
2554 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
2558 if (!strcmp(fmt, "wav49"))
2560 ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
2562 /* Make a temporary file instead of piping directly to sendmail, in case the mail
2564 if (!(p = vm_mkftemp(tmp))) {
2565 ast_log(LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
2567 *(vmu->email) = '\0';
2571 if (msgnum < 0 && imapgreetings) {
2572 init_mailstream(vms, GREETINGS_FOLDER);
2573 imap_delete_old_greeting(fn, vms);
2576 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);
2577 /* read mail file to memory */
2580 if (!(buf = ast_malloc(len + 1))) {
2581 ast_log(LOG_ERROR, "Can't allocate %ld bytes to read message\n", len + 1);
2584 *(vmu->email) = '\0';
2587 fread(buf, len, 1, p);
2588 ((char *)buf)[len] = '\0';
2589 INIT(&str, mail_string, buf, len);
2590 init_mailstream(vms, NEW_FOLDER);
2591 imap_mailbox_name(mailbox, sizeof(mailbox), vms, NEW_FOLDER, 1);
2592 if (!mail_append(vms->mailstream, mailbox, &str))
2593 ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
2597 ast_debug(3, "%s stored\n", fn);
2600 *(vmu->email) = '\0';
2606 static int messagecount(const char *context, const char *mailbox, const char *folder)
2611 struct ast_vm_user *vmu, vmus;
2612 struct vm_state *vms_p;
2614 int fold = folder_int(folder);
2616 if (ast_strlen_zero(mailbox))
2619 /* We have to get the user before we can open the stream! */
2620 /* ast_log(LOG_DEBUG, "Before find_user, context is %s and mailbox is %s\n", context, mailbox); */
2621 vmu = find_user(&vmus, context, mailbox);
2623 ast_log(LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailbox, context);
2626 /* No IMAP account available */
2627 if (vmu->imapuser[0] == '\0') {
2628 ast_log(LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
2633 /* No IMAP account available */
2634 if (vmu->imapuser[0] == '\0') {
2635 ast_log(LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
2640 /* check if someone is accessing this box right now... */
2641 vms_p = get_vm_state_by_imapuser(vmu->imapuser, 1);
2643 vms_p = get_vm_state_by_mailbox(mailbox, 1);
2646 ast_debug(3, "Returning before search - user is logged in\n");
2647 if (fold == 0) {/*INBOX*/
2648 return vms_p->newmessages;
2650 if (fold == 1) {/*Old messages*/
2651 return vms_p->oldmessages;
2655 /* add one if not there... */
2656 vms_p = get_vm_state_by_imapuser(vmu->imapuser, 0);
2658 vms_p = get_vm_state_by_mailbox(mailbox, 0);
2662 ast_debug(3, "Adding new vmstate for %s\n", vmu->imapuser);
2663 if (!(vms_p = ast_calloc(1, sizeof(*vms_p)))) {
2666 ast_copy_string(vms_p->imapuser, vmu->imapuser, sizeof(vms_p->imapuser));
2667 ast_copy_string(vms_p->username, mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
2668 vms_p->mailstream = NIL; /* save for access from interactive entry point */
2669 ast_debug(3, "Copied %s to %s\n", vmu->imapuser, vms_p->imapuser);
2671 /* set mailbox to INBOX! */
2672 ast_copy_string(vms_p->curbox, mbox(fold), sizeof(vms_p->curbox));
2673 init_vm_state(vms_p);
2674 vmstate_insert(vms_p);
2676 ret = init_mailstream(vms_p, fold);
2677 if (!vms_p->mailstream) {
2678 ast_log(LOG_ERROR, "Houston we have a problem - IMAP mailstream is NULL\n");
2682 pgm = mail_newsearchpgm ();
2683 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)mailbox);
2689 /* In the special case where fold is 1 (old messages) we have to do things a bit
2690 * differently. Old messages are stored in the INBOX but are marked as "seen"
2699 vms_p->vmArrayIndex = 0;
2700 mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
2702 vms_p->newmessages = vms_p->vmArrayIndex;
2704 vms_p->oldmessages = vms_p->vmArrayIndex;
2705 /*Freeing the searchpgm also frees the searchhdr*/
2706 mail_free_searchpgm(&pgm);
2708 return vms_p->vmArrayIndex;
2710 mail_ping(vms_p->mailstream);
2714 static int inboxcount(const char *mailbox_context, int *newmsgs, int *oldmsgs)
2716 char tmp[PATH_MAX] = "";
2726 ast_debug(3, "Mailbox is set to %s\n", mailbox_context);
2727 /* If no mailbox, return immediately */
2728 if (ast_strlen_zero(mailbox_context))
2731 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2732 context = strchr(tmp, '@');
2733 if (strchr(mailbox_context, ',')) {
2735 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2737 while ((cur = strsep(&mb, ", "))) {
2738 if (!ast_strlen_zero(cur)) {
2739 if (inboxcount(cur, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2756 context = "default";
2757 mailboxnc = (char *)mailbox_context;
2760 if ((*newmsgs = messagecount(context, mailboxnc, imapfolder)) < 0)
2764 if ((*oldmsgs = messagecount(context, mailboxnc, "Old")) < 0)
2771 static int has_voicemail(const char *mailbox, const char *folder)
2773 char tmp[256], *tmp2, *mbox, *context;
2774 ast_copy_string(tmp, mailbox, sizeof(tmp));
2776 if (strchr(tmp2, ',')) {
2777 while ((mbox = strsep(&tmp2, ","))) {
2778 if (!ast_strlen_zero(mbox)) {
2779 if (has_voicemail(mbox, folder))
2784 if ((context= strchr(tmp, '@')))
2787 context = "default";
2788 return messagecount(context, tmp, folder) ? 1 : 0;
2791 static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir)
2793 struct vm_state *sendvms = NULL, *destvms = NULL;
2794 char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
2795 if (!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 0))) {
2796 ast_log(LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
2799 if (!(destvms = get_vm_state_by_imapuser(recip->imapuser, 0))) {
2800 ast_log(LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
2803 snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
2804 if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(imbox)) == T))
2806 ast_log(LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
2811 #ifndef IMAP_STORAGE
2812 /* copy message only used by file storage */
2813 static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir)
2815 char fromdir[PATH_MAX], todir[PATH_MAX], frompath[PATH_MAX], topath[PATH_MAX];
2816 const char *frombox = mbox(imbox);
2819 ast_log(LOG_NOTICE, "Copying message from %s@%s to %s@%s\n", vmu->mailbox, vmu->context, recip->mailbox, recip->context);
2821 create_dirpath(todir, sizeof(todir), recip->context, recip->mailbox, "INBOX");
2824 make_dir(fromdir, sizeof(fromdir), vmu->context, vmu->mailbox, frombox);
2826 ast_copy_string(fromdir, dir, sizeof(fromdir));
2828 make_file(frompath, sizeof(frompath), fromdir, msgnum);
2829 make_dir(todir, sizeof(todir), recip->context, recip->mailbox, frombox);
2831 if (vm_lock_path(todir))
2832 return ERROR_LOCK_PATH;
2834 recipmsgnum = last_message_index(recip, todir) + 1;
2835 if (recipmsgnum < recip->maxmsg) {
2836 make_file(topath, sizeof(topath), todir, recipmsgnum);
2837 COPY(fromdir, msgnum, todir, recipmsgnum, recip->mailbox, recip->context, frompath, topath);
2839 ast_log(LOG_ERROR, "Recipient mailbox %s@%s is full\n", recip->mailbox, recip->context);
2841 ast_unlock_path(todir);
2842 notify_new_message(chan, recip, recipmsgnum, duration, fmt, S_OR(chan->cid.cid_num, NULL), S_OR(chan->cid.cid_name, NULL));
2847 #if !(defined(IMAP_STORAGE) || defined(ODBC_STORAGE))
2848 static int messagecount(const char *context, const char *mailbox, const char *folder)
2850 return __has_voicemail(context, mailbox, folder, 0);
2854 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit)
2861 /* If no mailbox, return immediately */
2862 if (ast_strlen_zero(mailbox))
2865 if (ast_strlen_zero(folder))
2867 if (ast_strlen_zero(context))
2868 context = "default";
2870 snprintf(fn, sizeof(fn), "%s%s/%s/%s", VM_SPOOL_DIR, context, mailbox, folder);
2872 if (!(dir = opendir(fn)))
2875 while ((de = readdir(dir))) {
2876 if (!strncasecmp(de->d_name, "msg", 3)) {
2880 } else if (!strncasecmp(de->d_name + 8, "txt", 3))
2891 static int has_voicemail(const char *mailbox, const char *folder)
2893 char tmp[256], *tmp2 = tmp, *mbox, *context;
2894 ast_copy_string(tmp, mailbox, sizeof(tmp));
2895 while ((mbox = strsep(&tmp2, ","))) {
2896 if ((context = strchr(mbox, '@')))
2899 context = "default";
2900 if (__has_voicemail(context, mbox, folder, 1))
2907 static int inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
2912 /* If no mailbox, return immediately */
2913 if (ast_strlen_zero(mailbox))
2921 if (strchr(mailbox, ',')) {
2925 ast_copy_string(tmp, mailbox, sizeof(tmp));
2927 while ((cur = strsep(&mb, ", "))) {
2928 if (!ast_strlen_zero(cur)) {
2929 if (inboxcount(cur, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2942 ast_copy_string(tmp, mailbox, sizeof(tmp));
2944 if ((context = strchr(tmp, '@')))
2947 context = "default";
2950 *newmsgs = __has_voicemail(context, tmp, "INBOX", 0);
2952 *oldmsgs = __has_voicemail(context, tmp, "Old", 0);
2959 static void run_externnotify(char *context, char *extension)
2961 char arguments[255];
2962 char ext_context[256] = "";
2963 int newvoicemails = 0, oldvoicemails = 0;
2964 struct ast_smdi_mwi_message *mwi_msg;
2966 if (!ast_strlen_zero(context))
2967 snprintf(ext_context, sizeof(ext_context), "%s@%s", extension, context);
2969 ast_copy_string(ext_context, extension, sizeof(ext_context));
2972 if (ast_app_has_voicemail(ext_context, NULL))
2973 ast_smdi_mwi_set(smdi_iface, extension);
2975 ast_smdi_mwi_unset(smdi_iface, extension);
2977 if ((mwi_msg = ast_smdi_mwi_message_wait(smdi_iface, SMDI_MWI_WAIT_TIMEOUT))) {
2978 ast_log(LOG_ERROR, "Error executing SMDI MWI change for %s on %s\n", extension, smdi_iface->name);
2979 if (!strncmp(mwi_msg->cause, "INV", 3))
2980 ast_log(LOG_ERROR, "Invalid MWI extension: %s\n", mwi_msg->fwd_st);
2981 else if (!strncmp(mwi_msg->cause, "BLK", 3))
2982 ast_log(LOG_WARNING, "MWI light was already on or off for %s\n", mwi_msg->fwd_st);
2983 ast_log(LOG_WARNING, "The switch reported '%s'\n", mwi_msg->cause);
2984 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
2986 ast_debug(1, "Successfully executed SMDI MWI change for %s on %s\n", extension, smdi_iface->name);
2990 if (!ast_strlen_zero(externnotify)) {
2991 if (inboxcount(ext_context, &newvoicemails, &oldvoicemails)) {
2992 ast_log(LOG_ERROR, "Problem in calculating number of voicemail messages available for extension %s\n", extension);
2994 snprintf(arguments, sizeof(arguments), "%s %s %s %d&", externnotify, context, extension, newvoicemails);
2995 ast_debug(1, "Executing %s\n", arguments);
2996 ast_safe_system(arguments);
3001 struct leave_vm_options {
3003 signed char record_gain;
3007 static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_options *options)
3010 int newmsgs, oldmsgs;
3011 struct vm_state *vms = NULL;
3013 char txtfile[PATH_MAX], tmptxtfile[PATH_MAX];
3029 char dir[PATH_MAX], tmpdir[PATH_MAX];
3031 char prefile[PATH_MAX] = "";
3032 char tempfile[PATH_MAX] = "";
3033 char ext_context[256] = "";
3036 char ecodes[17] = "#";
3037 char tmp[1024] = "", *tmpptr;
3038 struct ast_vm_user *vmu;
3039 struct ast_vm_user svm;
3040 const char *category = NULL, *code, *alldtmf = "0123456789ABCD*#";
3042 ast_copy_string(tmp, ext, sizeof(tmp));
3044 if ((context = strchr(tmp, '@'))) {
3046 tmpptr = strchr(context, '&');
3048 tmpptr = strchr(ext, '&');
3054 category = pbx_builtin_getvar_helper(chan, "VM_CATEGORY");
3056 ast_debug(3, "Before find_user\n");
3057 if (!(vmu = find_user(&svm, context, ext))) {
3058 ast_log(LOG_WARNING, "No entry in voicemail config file for '%s'\n", ext);
3059 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
3062 /* Setup pre-file if appropriate */
3063 if (strcmp(vmu->context, "default"))
3064 snprintf(ext_context, sizeof(ext_context), "%s@%s", ext, vmu->context);
3066 ast_copy_string(ext_context, vmu->mailbox, sizeof(ext_context));
3067 if (ast_test_flag(options, OPT_BUSY_GREETING)) {
3068 snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, ext);
3069 } else if (ast_test_flag(options, OPT_UNAVAIL_GREETING)) {
3070 snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, ext);
3072 snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", VM_SPOOL_DIR, vmu->context, ext);
3073 if ((res = create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, ext, "tmp"))) {
3074 ast_log(LOG_WARNING, "Failed to make directory (%s)\n", tempfile);
3077 RETRIEVE(tempfile, -1, ext, context);
3078 if (ast_fileexists(tempfile, NULL, NULL) > 0)
3079 ast_copy_string(prefile, tempfile, sizeof(prefile));
3080 DISPOSE(tempfile, -1);
3081 /* It's easier just to try to make it than to check for its existence */
3082 create_dirpath(dir, sizeof(dir), vmu->context, ext, "INBOX");
3084 /* Check current or macro-calling context for special extensions */
3085 if (ast_test_flag(vmu, VM_OPERATOR)) {
3086 if (!ast_strlen_zero(vmu->exit)) {
3087 if (ast_exists_extension(chan, vmu->exit, "o", 1, chan->cid.cid_num)) {
3088 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
3091 } else if (ast_exists_extension(chan, chan->context, "o", 1, chan->cid.cid_num)) {
3092 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
3095 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num)) {
3096 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
3101 if (!ast_strlen_zero(vmu->exit)) {
3102 if (ast_exists_extension(chan, vmu->exit, "a", 1, chan->cid.cid_num))
3103 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
3104 } else if (ast_exists_extension(chan, chan->context, "a", 1, chan->cid.cid_num))
3105 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
3106 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "a", 1, chan->cid.cid_num)) {
3107 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
3111 if (ast_test_flag(options, OPT_DTMFEXIT)) {
3112 for (code = alldtmf; *code; code++) {
3115 if (strchr(ecodes, e[0]) == NULL && ast_canmatch_extension(chan, chan->context, e, 1, chan->cid.cid_num))
3116 strncat(ecodes, e, sizeof(ecodes) - strlen(ecodes) - 1);
3120 /* Play the beginning intro if desired */
3121 if (!ast_strlen_zero(prefile)) {
3122 res = play_greeting(chan, vmu, prefile, ecodes);
3124 /* The file did not exist */
3125 ast_debug(1, "%s doesn't exist, doing what we can\n", prefile);
3126 res = invent_message(chan, vmu, ext, ast_test_flag(options, OPT_BUSY_GREETING), ecodes);
3129 ast_debug(1, "Hang up during prefile playback\n");
3131 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
3136 /* On a '#' we skip the instructions */
3137 ast_set_flag(options, OPT_SILENT);
3140 if (!res && !ast_test_flag(options, OPT_SILENT)) {
3141 res = ast_stream_and_wait(chan, INTRO, ecodes);
3143 ast_set_flag(options, OPT_SILENT);
3148 ast_stopstream(chan);
3149 /* Check for a '*' here in case the caller wants to escape from voicemail to something
3150 other than the operator -- an automated attendant or mailbox login for example */
3151 if (!ast_strlen_zero(vmu->exit) && (res == '*')) {
3152 chan->exten[0] = 'a';
3153 chan->exten[1] = '\0';
3154 if (!ast_strlen_zero(vmu->exit)) {
3155 ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
3156 } else if (ausemacro && !ast_strlen_zero(chan->macrocontext)) {
3157 ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
3161 pbx_builtin_setvar_helper(chan, "VMSTATUS", "USEREXIT");
3165 /* Check for a '0' here */
3166 if (ast_test_flag(vmu, VM_OPERATOR) && res == '0') {
3168 if (ouseexten || ousemacro) {
3169 chan->exten[0] = 'o';
3170 chan->exten[1] = '\0';
3171 if (!ast_strlen_zero(vmu->exit)) {
3172 ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
3173 } else if (ousemacro && !ast_strlen_zero(chan->macrocontext)) {
3174 ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
3176 ast_play_and_wait(chan, "transfer");
3179 pbx_builtin_setvar_helper(chan, "VMSTATUS", "USEREXIT");
3184 /* Allow all other digits to exit Voicemail and return to the dialplan */
3185 if (ast_test_flag(options, OPT_DTMFEXIT) && res > 0) {
3186 if (!ast_strlen_zero(options->exitcontext))
3187 ast_copy_string(chan->context, options->exitcontext, sizeof(chan->context));
3189 pbx_builtin_setvar_helper(chan, "VMSTATUS", "USEREXIT");
3195 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
3198 /* The meat of recording the message... All the announcements and beeps have been played*/
3199 ast_copy_string(fmt, vmfmts, sizeof(fmt));
3200 if (!ast_strlen_zero(fmt)) {
3204 /* Is ext a mailbox? */
3205 /* must open stream for this user to get info! */
3206 res = inboxcount(ext_context, &newmsgs, &oldmsgs);
3208 ast_log(LOG_NOTICE, "Can not leave voicemail, unable to count messages\n");
3211 if (!(vms = get_vm_state_by_mailbox(ext, 0))) {
3212 /*It is possible under certain circumstances that inboxcount did not create a vm_state when it was needed. This is a catchall which will
3214 if (!(vms = ast_calloc(1, sizeof(*vms)))) {
3215 ast_log(LOG_ERROR, "Couldn't allocate necessary space\n");
3218 ast_copy_string(vms->imapuser, vmu->imapuser, sizeof(vms->imapuser));
3219 ast_copy_string(vms->username, ext, sizeof(vms->username));
3220 vms->mailstream = NIL;
3221 ast_debug(3, "Copied %s to %s\n", vmu->imapuser, vms->imapuser);
3223 ast_copy_string(vms->curbox, mbox(0), sizeof(vms->curbox));
3225 vmstate_insert(vms);
3226 vms = get_vm_state_by_mailbox(ext, 0);
3230 /* here is a big difference! We add one to it later */
3231 msgnum = newmsgs + oldmsgs;
3232 ast_debug(3, "Messagecount set to %d\n", msgnum);
3233 snprintf(fn, sizeof(fn), "%s/imap/msg%s%04d", VM_SPOOL_DIR, vmu->mailbox, msgnum);
3234 /* set variable for compatibility */
3235 pbx_builtin_setvar_helper(chan, "VM_MESSAGEFILE", "IMAP_STORAGE");
3237 /* Check if mailbox is full */
3238 check_quota(vms, imapfolder);
3239 if (vms->quota_limit && vms->quota_usage >= vms->quota_limit) {
3240 ast_debug(1, "*** QUOTA EXCEEDED!! %u >= %u\n", vms->quota_usage, vms->quota_limit);
3241 ast_play_and_wait(chan, "vm-mailboxfull");
3245 /* Check if we have exceeded maxmsg */
3246 if (msgnum >= vmu->maxmsg) {
3247 ast_log(LOG_WARNING, "Unable to leave message since we will exceed the maximum number of messages allowed (%u > %u)\n", msgnum, vmu->maxmsg);
3248 ast_play_and_wait(chan, "vm-mailboxfull");
3253 if (count_messages(vmu, dir) >= vmu->maxmsg) {
3254 res = ast_streamfile(chan, "vm-mailboxfull", chan->language);
3256 res = ast_waitstream(chan, "");
3257 ast_log(LOG_WARNING, "No more messages possible\n");
3258 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
3263 snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir);
3264 txtdes = mkstemp(tmptxtfile);
3265 chmod(tmptxtfile, VOICEMAIL_FILE_MODE & ~my_umask);
3267 res = ast_streamfile(chan, "vm-mailboxfull", chan->language);
3269 res = ast_waitstream(chan, "");
3270 ast_log(LOG_ERROR, "Unable to create message file: %s\n", strerror(errno));
3271 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
3275 /* Now play the beep once we have the message number for our next message. */
3277 /* Unless we're *really* silent, try to send the beep */
3278 res = ast_stream_and_wait(chan, "beep", "");
3281 /* Store information in real-time storage */
3282 if (ast_check_realtime("voicemail_data")) {
3283 snprintf(priority, sizeof(priority), "%d", chan->priority);
3284 snprintf(origtime, sizeof(origtime), "%ld", (long)time(NULL));
3285 get_date(date, sizeof(date));
3286 rtmsgid = ast_store_realtime("voicemail_data", "origmailbox", ext, "context", chan->context, "macrocontext", chan->macrocontext, "exten", chan->exten, "priority", priority, "callerchan", chan->name, "callerid", ast_callerid_merge(callerid, sizeof(callerid), chan->cid.cid_name, chan->cid.cid_num, "Unknown"), "origdate", date, "origtime", origtime, "category", category ? category : "", NULL);
3289 /* Store information */
3290 txt = fdopen(txtdes, "w+");
3292 get_date(date, sizeof(date));
3295 "; Message Information file\n"
3314 ast_callerid_merge(callerid, sizeof(callerid), S_OR(chan->cid.cid_name, NULL), S_OR(chan->cid.cid_num, NULL), "Unknown"),
3315 date, (long)time(NULL),
3316 category ? category : "");
3318 ast_log(LOG_WARNING, "Error opening text file for output\n");
3320 res = play_record_review(chan, NULL, tmptxtfile, vmu->maxsecs, fmt, 1, vmu, &duration, NULL, options->record_gain, vms);
3322 res = play_record_review(chan, NULL, tmptxtfile, vmu->maxsecs, fmt, 1, vmu, &duration, NULL, options->record_gain, NULL);
3326 if (duration < vmminsecs) {
3328 if (option_verbose > 2)
3329 ast_verbose(VERBOSE_PREFIX_3 "Recording was %d seconds long but needs to be at least %d - abandoning\n", duration, vmminsecs);
3330 ast_filedelete(tmptxtfile, NULL);
3332 if (ast_check_realtime("voicemail_data")) {
3333 snprintf(tmpid, sizeof(tmpid), "%d", rtmsgid);
3334 ast_destroy_realtime("voicemail_data", "id", tmpid, NULL);
3337 fprintf(txt, "duration=%d\n", duration);
3339 if (vm_lock_path(dir)) {
3340 ast_log(LOG_ERROR, "Couldn't lock directory %s. Voicemail will be lost.\n", dir);
3342 ast_filedelete(tmptxtfile, NULL);
3344 } else if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
3345 ast_debug(1, "The recorded media file is gone, so we should remove the .txt file too!\n");
3347 ast_unlock_path(dir);
3348 if (ast_check_realtime("voicemail_data")) {
3349 snprintf(tmpid, sizeof(tmpid), "%d", rtmsgid);
3350 ast_destroy_realtime("voicemail_data", "id", tmpid, NULL);
3353 #ifndef IMAP_STORAGE
3354 msgnum = last_message_index(vmu, dir) + 1;
3356 make_file(fn, sizeof(fn), dir, msgnum);
3358 /* assign a variable with the name of the voicemail file */
3359 #ifndef IMAP_STORAGE
3360 pbx_builtin_setvar_helper(chan, "VM_MESSAGEFILE", fn);
3362 pbx_builtin_setvar_helper(chan, "VM_MESSAGEFILE", "IMAP_STORAGE");
3365 snprintf(txtfile, sizeof(txtfile), "%s.txt", fn);
3366 ast_filerename(tmptxtfile, fn, NULL);
3367 rename(tmptxtfile, txtfile);
3369 /* Properly set permissions on voicemail text descriptor file.
3370 Unfortunately mkstemp() makes this file 0600 on most unix systems. */
3371 if (chmod(txtfile, VOICEMAIL_FILE_MODE) < 0)
3372 ast_log(LOG_ERROR, "Couldn't set permissions on voicemail text file %s: %s", txtfile, strerror(errno));
3374 ast_unlock_path(dir);
3375 if (ast_check_realtime("voicemail_data")) {
3376 snprintf(tmpid, sizeof(tmpid), "%d", rtmsgid);
3377 snprintf(tmpdur, sizeof(tmpdur), "%d", duration);
3378 ast_update_realtime("voicemail_data", "id", tmpid, "filename", fn, "duration", tmpdur, NULL);
3380 /* We must store the file first, before copying the message, because
3381 * ODBC storage does the entire copy with SQL.
3383 if (ast_fileexists(fn, NULL, NULL) > 0) {
3384 STORE(dir, vmu->mailbox, vmu->context, msgnum, chan, vmu, fmt, duration, vms);
3387 /* Are there to be more recipients of this message? */
3389 struct ast_vm_user recipu, *recip;
3390 char *exten, *context;
3392 exten = strsep(&tmpptr, "&");
3393 context = strchr(exten, '@');
3398 if ((recip = find_user(&recipu, context, exten))) {
3399 copy_message(chan, vmu, 0, msgnum, duration, recip, fmt, dir);
3403 /* Notification and disposal needs to happen after the copy, though. */
3404 if (ast_fileexists(fn, NULL, NULL)) {
3405 notify_new_message(chan, vmu, msgnum, duration, fmt, S_OR(chan->cid.cid_num, NULL), S_OR(chan->cid.cid_name, NULL));
3406 DISPOSE(dir, msgnum);
3416 if (duration < vmminsecs)
3417 /* XXX We should really give a prompt too short/option start again, with leave_vm_out called only after a timeout XXX */
3418 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
3420 pbx_builtin_setvar_helper(chan, "VMSTATUS", "SUCCESS");
3422 ast_log(LOG_WARNING, "No format for saving voicemail?\n");
3429 #ifndef IMAP_STORAGE
3430 static int resequence_mailbox(struct ast_vm_user *vmu, char *dir)
3432 /* we know max messages, so stop process when number is hit */
3438 if (vm_lock_path(dir))
3439 return ERROR_LOCK_PATH;
3441 for (x = 0, dest = 0; x < vmu->maxmsg; x++) {
3442 make_file(sfn, sizeof(sfn), dir, x);
3443 if (EXISTS(dir, x, sfn, NULL)) {
3446 make_file(dfn, sizeof(dfn), dir, dest);
3447 RENAME(dir, x, vmu->mailbox, vmu->context, dir, dest, sfn, dfn);
3453 ast_unlock_path(dir);
3459 static int say_and_wait(struct ast_channel *chan, int num, const char *language)
3462 d = ast_say_number(chan, num, AST_DIGIT_ANY, language, NULL);
3466 static int save_to_folder(struct ast_vm_user *vmu, struct vm_state *vms, int msg, int box)
3469 /* we must use mbox(x) folder names, and copy the message there */
3474 /* if save to Old folder, just leave in INBOX */
3475 if (box == 1) return 10;
3476 /* get the real IMAP message number for this message */
3477 snprintf(sequence, sizeof(sequence), "%ld", vms->msgArray[msg]);
3478 ast_debug(3, "Copying sequence %s to mailbox %s\n", sequence, mbox(box));
3479 res = mail_copy(vms->mailstream, sequence, (char *)mbox(box));
3480 if (res == 1) return 0;
3483 char *dir = vms->curdir;
3484 char *username = vms->username;
3485 char *context = vmu->context;
3488 char ddir[PATH_MAX];