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 BASEMAXINLINE 256
201 #define BASELINELEN 72
202 #define BASEMAXINLINE 256
205 #define MAX_DATETIME_FORMAT 512
206 #define MAX_NUM_CID_CONTEXTS 10
208 #define VM_REVIEW (1 << 0)
209 #define VM_OPERATOR (1 << 1)
210 #define VM_SAYCID (1 << 2)
211 #define VM_SVMAIL (1 << 3)
212 #define VM_ENVELOPE (1 << 4)
213 #define VM_SAYDURATION (1 << 5)
214 #define VM_SKIPAFTERCMD (1 << 6)
215 #define VM_FORCENAME (1 << 7) /*!< Have new users record their name */
216 #define VM_FORCEGREET (1 << 8) /*!< Have new users record their greetings */
217 #define VM_PBXSKIP (1 << 9)
218 #define VM_DIRECFORWARD (1 << 10) /*!< directory_forward */
219 #define VM_ATTACH (1 << 11)
220 #define VM_DELETE (1 << 12)
221 #define VM_ALLOCED (1 << 13)
222 #define VM_SEARCH (1 << 14)
223 #define VM_TEMPGREETWARN (1 << 15) /*!< Remind user tempgreeting is set */
224 #define VM_MOVEHEARD (1 << 16) /*!< Move a "heard" message to Old after listening to it */
225 #define ERROR_LOCK_PATH -100
238 OPT_SILENT = (1 << 0),
239 OPT_BUSY_GREETING = (1 << 1),
240 OPT_UNAVAIL_GREETING = (1 << 2),
241 OPT_RECORDGAIN = (1 << 3),
242 OPT_PREPEND_MAILBOX = (1 << 4),
243 OPT_AUTOPLAY = (1 << 6),
247 OPT_ARG_RECORDGAIN = 0,
248 OPT_ARG_PLAYFOLDER = 1,
249 /* This *must* be the last value in this enum! */
250 OPT_ARG_ARRAY_SIZE = 2,
253 AST_APP_OPTIONS(vm_app_options, {
254 AST_APP_OPTION('s', OPT_SILENT),
255 AST_APP_OPTION('b', OPT_BUSY_GREETING),
256 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
257 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
258 AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
259 AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
262 static int load_config(int reload);
264 /*! \page vmlang Voicemail Language Syntaxes Supported
266 \par Syntaxes supported, not really language codes.
273 \arg \b pt - Portuguese
274 \arg \b pt_BR - Portuguese (Brazil)
276 \arg \b no - Norwegian
278 \arg \b tw - Chinese (Taiwan)
279 \arg \b ua - Ukrainian
281 German requires the following additional soundfile:
282 \arg \b 1F einE (feminine)
284 Spanish requires the following additional soundfile:
285 \arg \b 1M un (masculine)
287 Dutch, Portuguese & Spanish require the following additional soundfiles:
288 \arg \b vm-INBOXs singular of 'new'
289 \arg \b vm-Olds singular of 'old/heard/read'
292 \arg \b vm-INBOX nieuwe (nl)
293 \arg \b vm-Old oude (nl)
296 \arg \b vm-new-a 'new', feminine singular accusative
297 \arg \b vm-new-e 'new', feminine plural accusative
298 \arg \b vm-new-ych 'new', feminine plural genitive
299 \arg \b vm-old-a 'old', feminine singular accusative
300 \arg \b vm-old-e 'old', feminine plural accusative
301 \arg \b vm-old-ych 'old', feminine plural genitive
302 \arg \b digits/1-a 'one', not always same as 'digits/1'
303 \arg \b digits/2-ie 'two', not always same as 'digits/2'
306 \arg \b vm-nytt singular of 'new'
307 \arg \b vm-nya plural of 'new'
308 \arg \b vm-gammalt singular of 'old'
309 \arg \b vm-gamla plural of 'old'
310 \arg \b digits/ett 'one', not always same as 'digits/1'
313 \arg \b vm-ny singular of 'new'
314 \arg \b vm-nye plural of 'new'
315 \arg \b vm-gammel singular of 'old'
316 \arg \b vm-gamle plural of 'old'
324 Ukrainian requires the following additional soundfile:
325 \arg \b vm-nove 'nove'
326 \arg \b vm-stare 'stare'
327 \arg \b digits/ua/1e 'odne'
329 Italian requires the following additional soundfile:
333 \arg \b vm-nuovi new plural
334 \arg \b vm-vecchio old
335 \arg \b vm-vecchi old plural
337 Chinese (Taiwan) requires the following additional soundfile:
338 \arg \b vm-tong A class-word for call (tong1)
339 \arg \b vm-ri A class-word for day (ri4)
340 \arg \b vm-you You (ni3)
341 \arg \b vm-haveno Have no (mei2 you3)
342 \arg \b vm-have Have (you3)
343 \arg \b vm-listen To listen (yao4 ting1)
346 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
347 spelled among others when you have to change folder. For the above reasons, vm-INBOX
348 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
357 unsigned char iobuf[BASEMAXINLINE];
360 /*! Structure for linked list of users
361 * Use ast_vm_user_destroy() to free one of these structures. */
363 char context[AST_MAX_CONTEXT]; /*!< Voicemail context */
364 char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
365 char password[80]; /*!< Secret pin code, numbers only */
366 char fullname[80]; /*!< Full name, for directory app */
367 char email[80]; /*!< E-mail address */
368 char pager[80]; /*!< E-mail address to pager (no attachment) */
369 char serveremail[80]; /*!< From: Mail address */
370 char mailcmd[160]; /*!< Configurable mail command */
371 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
372 char zonetag[80]; /*!< Time zone */
375 char uniqueid[20]; /*!< Unique integer identifier */
377 char attachfmt[20]; /*!< Attachment format */
378 unsigned int flags; /*!< VM_ flags */
380 int maxmsg; /*!< Maximum number of msgs per folder for this mailbox */
381 int maxsecs; /*!< Maximum number of seconds per message for this mailbox */
383 char imapuser[80]; /*!< IMAP server login */
384 char imappassword[80]; /*!< IMAP server password if authpassword not defined */
386 double volgain; /*!< Volume gain for voicemails sent via email */
387 AST_LIST_ENTRY(ast_vm_user) list;
390 /*! Voicemail time zones */
392 AST_LIST_ENTRY(vm_zone) list;
395 char msg_format[512];
398 /*! Voicemail mailbox state */
402 char curdir[PATH_MAX];
403 char vmbox[PATH_MAX];
415 int updated; /*!< decremented on each mail check until 1 -allows delay */
417 MAILSTREAM *mailstream;
419 char imapuser[80]; /*!< IMAP server login */
421 unsigned int quota_limit;
422 unsigned int quota_usage;
423 struct vm_state *persist_vms;
429 static char odbc_database[80];
430 static char odbc_table[80];
431 #define RETRIEVE(a,b,c,d) retrieve_file(a,b)
432 #define DISPOSE(a,b) remove_file(a,b)
433 #define STORE(a,b,c,d,e,f,g,h,i) store_file(a,b,c,d)
434 #define EXISTS(a,b,c,d) (message_exists(a,b))
435 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
436 #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
437 #define DELETE(a,b,c) (delete_file(a,b))
440 #define RETRIEVE(a,b,c,d) (imap_retrieve_file(a,b,c,d ))
441 #define DISPOSE(a,b) (imap_remove_file(a,b))
442 #define STORE(a,b,c,d,e,f,g,h,i) (imap_store_file(a,b,c,d,e,f,g,h,i))
443 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
444 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
445 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
446 #define IMAP_DELETE(a,b,c,d) (vm_imap_delete(b,d))
447 #define DELETE(a,b,c) (vm_delete(c))
449 #define RETRIEVE(a,b,c,d)
451 #define STORE(a,b,c,d,e,f,g,h,i)
452 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
453 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
454 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
455 #define DELETE(a,b,c) (vm_delete(c))
459 static char VM_SPOOL_DIR[PATH_MAX];
461 static char ext_pass_cmd[128];
465 #define PWDCHANGE_INTERNAL (1 << 1)
466 #define PWDCHANGE_EXTERNAL (1 << 2)
467 static int pwdchange = PWDCHANGE_INTERNAL;
470 #define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
473 # define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
475 # define tdesc "Comedian Mail (Voicemail System)"
479 static char userscontext[AST_MAX_EXTENSION] = "default";
481 static char *addesc = "Comedian Mail";
483 static char *synopsis_vm = "Leave a Voicemail message";
485 static char *descrip_vm =
486 " VoiceMail(mailbox[@context][&mailbox[@context]][...][,options]): This\n"
487 "application allows the calling party to leave a message for the specified\n"
488 "list of mailboxes. When multiple mailboxes are specified, the greeting will\n"
489 "be taken from the first mailbox specified. Dialplan execution will stop if the\n"
490 "specified mailbox does not exist.\n"
491 " The Voicemail application will exit if any of the following DTMF digits are\n"
493 " 0 - Jump to the 'o' extension in the current dialplan context.\n"
494 " * - Jump to the 'a' extension in the current dialplan context.\n"
495 " This application will set the following channel variable upon completion:\n"
496 " VMSTATUS - This indicates the status of the execution of the VoiceMail\n"
497 " application. The possible values are:\n"
498 " SUCCESS | USEREXIT | FAILED\n\n"
500 " b - Play the 'busy' greeting to the calling party.\n"
501 " g(#) - Use the specified amount of gain when recording the voicemail\n"
502 " message. The units are whole-number decibels (dB).\n"
503 " s - Skip the playback of instructions for leaving a message to the\n"
505 " u - Play the 'unavailable' greeting.\n";
507 static char *synopsis_vmain = "Check Voicemail messages";
509 static char *descrip_vmain =
510 " VoiceMailMain([mailbox][@context][,options]): This application allows the\n"
511 "calling party to check voicemail messages. A specific mailbox, and optional\n"
512 "corresponding context, may be specified. If a mailbox is not provided, the\n"
513 "calling party will be prompted to enter one. If a context is not specified,\n"
514 "the 'default' context will be used.\n\n"
516 " p - Consider the mailbox parameter as a prefix to the mailbox that\n"
517 " is entered by the caller.\n"
518 " g(#) - Use the specified amount of gain when recording a voicemail\n"
519 " message. The units are whole-number decibels (dB).\n"
520 " s - Skip checking the passcode for the mailbox.\n"
521 " a(#) - Skip folder prompt and go directly to folder specified.\n"
522 " Defaults to INBOX\n";
524 static char *synopsis_vm_box_exists =
525 "Check to see if Voicemail mailbox exists";
527 static char *descrip_vm_box_exists =
528 " MailboxExists(mailbox[@context][,options]): Check to see if the specified\n"
529 "mailbox exists. If no voicemail context is specified, the 'default' context\n"
531 " This application will set the following channel variable upon completion:\n"
532 " VMBOXEXISTSSTATUS - This will contain the status of the execution of the\n"
533 " MailboxExists application. Possible values include:\n"
534 " SUCCESS | FAILED\n\n"
535 " Options: (none)\n";
537 static char *synopsis_vmauthenticate = "Authenticate with Voicemail passwords";
539 static char *descrip_vmauthenticate =
540 " VMAuthenticate([mailbox][@context][,options]): This application behaves the\n"
541 "same way as the Authenticate application, but the passwords are taken from\n"
543 " If the mailbox is specified, only that mailbox's password will be considered\n"
544 "valid. If the mailbox is not specified, the channel variable AUTH_MAILBOX will\n"
545 "be set with the authenticated mailbox.\n\n"
547 " s - Skip playing the initial prompts.\n";
549 /* Leave a message */
550 static char *app = "VoiceMail";
552 /* Check mail, control, etc */
553 static char *app2 = "VoiceMailMain";
555 static char *app3 = "MailboxExists";
556 static char *app4 = "VMAuthenticate";
558 static AST_LIST_HEAD_STATIC(users, ast_vm_user);
559 static AST_LIST_HEAD_STATIC(zones, vm_zone);
560 static int maxsilence;
562 static int silencethreshold = 128;
563 static char serveremail[80];
564 static char mailcmd[160]; /* Configurable mail cmd */
565 static char externnotify[160];
566 static struct ast_smdi_interface *smdi_iface = NULL;
567 static char vmfmts[80];
568 static double volgain;
569 static int vmminsecs;
570 static int vmmaxsecs;
573 static int maxlogins;
575 /*! Poll mailboxes for changes since there is something external to
576 * app_voicemail that may change them. */
577 static unsigned int poll_mailboxes;
579 /*! Polling frequency */
580 static unsigned int poll_freq;
581 /*! By default, poll every 30 seconds */
582 #define DEFAULT_POLL_FREQ 30
584 AST_MUTEX_DEFINE_STATIC(poll_lock);
585 static ast_cond_t poll_cond = PTHREAD_COND_INITIALIZER;
586 static pthread_t poll_thread = AST_PTHREADT_NULL;
587 static unsigned char poll_thread_run;
589 /*! Subscription to ... MWI event subscriptions */
590 static struct ast_event_sub *mwi_sub_sub;
591 /*! Subscription to ... MWI event un-subscriptions */
592 static struct ast_event_sub *mwi_unsub_sub;
595 * \brief An MWI subscription
597 * This is so we can keep track of which mailboxes are subscribed to.
598 * This way, we know which mailboxes to poll when the pollmailboxes
599 * option is being used.
602 AST_RWLIST_ENTRY(mwi_sub) entry;
609 static AST_RWLIST_HEAD_STATIC(mwi_subs, mwi_sub);
611 /* custom audio control prompts for voicemail playback */
612 static char listen_control_forward_key[12];
613 static char listen_control_reverse_key[12];
614 static char listen_control_pause_key[12];
615 static char listen_control_restart_key[12];
616 static char listen_control_stop_key[12];
618 /* custom password sounds */
619 static char vm_password[80] = "vm-password";
620 static char vm_newpassword[80] = "vm-newpassword";
621 static char vm_passchanged[80] = "vm-passchanged";
622 static char vm_reenterpassword[80] = "vm-reenterpassword";
623 static char vm_mismatch[80] = "vm-mismatch";
625 static struct ast_flags globalflags = {0};
627 static int saydurationminfo;
629 static char dialcontext[AST_MAX_CONTEXT] = "";
630 static char callcontext[AST_MAX_CONTEXT] = "";
631 static char exitcontext[AST_MAX_CONTEXT] = "";
633 static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
636 static char *emailbody = NULL;
637 static char *emailsubject = NULL;
638 static char *pagerbody = NULL;
639 static char *pagersubject = NULL;
640 static char fromstring[100];
641 static char pagerfromstring[100];
642 static char emailtitle[100];
643 static char charset[32] = "ISO-8859-1";
645 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
646 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
647 static int adsiver = 1;
648 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
650 /* Forward declarations - generic */
651 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu,int box);
652 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);
653 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
654 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
655 char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
656 signed char record_gain, struct vm_state *vms);
657 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
658 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
659 static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu, int msgnum, long duration, char *fmt, char *cidnum, char *cidname);
660 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);
661 static void apply_options(struct ast_vm_user *vmu, const char *options);
662 static int is_valid_dtmf(const char *key);
664 #if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
665 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
670 static void populate_defaults(struct ast_vm_user *vmu)
672 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
673 if (saydurationminfo)
674 vmu->saydurationm = saydurationminfo;
675 ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
676 ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
677 ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
679 vmu->maxsecs = vmmaxsecs;
681 vmu->maxmsg = maxmsg;
682 vmu->volgain = volgain;
685 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
688 if (!strcasecmp(var, "attach")) {
689 ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
690 } else if (!strcasecmp(var, "attachfmt")) {
691 ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
692 } else if (!strcasecmp(var, "serveremail")) {
693 ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
694 } else if (!strcasecmp(var, "language")) {
695 ast_copy_string(vmu->language, value, sizeof(vmu->language));
696 } else if (!strcasecmp(var, "tz")) {
697 ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
699 } else if (!strcasecmp(var, "imapuser")) {
700 ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
701 } else if (!strcasecmp(var, "imappassword")) {
702 ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
704 } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
705 ast_set2_flag(vmu, ast_true(value), VM_DELETE);
706 } else if (!strcasecmp(var, "saycid")){
707 ast_set2_flag(vmu, ast_true(value), VM_SAYCID);
708 } else if (!strcasecmp(var,"sendvoicemail")){
709 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL);
710 } else if (!strcasecmp(var, "review")){
711 ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
712 } else if (!strcasecmp(var, "tempgreetwarn")){
713 ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);
714 } else if (!strcasecmp(var, "operator")){
715 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);
716 } else if (!strcasecmp(var, "envelope")){
717 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);
718 } else if (!strcasecmp(var, "moveheard")){
719 ast_set2_flag(vmu, ast_true(value), VM_MOVEHEARD);
720 } else if (!strcasecmp(var, "sayduration")){
721 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);
722 } else if (!strcasecmp(var, "saydurationm")){
723 if (sscanf(value, "%d", &x) == 1) {
724 vmu->saydurationm = x;
726 ast_log(LOG_WARNING, "Invalid min duration for say duration\n");
728 } else if (!strcasecmp(var, "forcename")){
729 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);
730 } else if (!strcasecmp(var, "forcegreetings")){
731 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);
732 } else if (!strcasecmp(var, "callback")) {
733 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
734 } else if (!strcasecmp(var, "dialout")) {
735 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
736 } else if (!strcasecmp(var, "exitcontext")) {
737 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
738 } else if (!strcasecmp(var, "maxmessage") || !strcasecmp(var, "maxsecs")) {
739 if (vmu->maxsecs <= 0) {
740 ast_log(LOG_WARNING, "Invalid max message length of %s. Using global value %d\n", value, vmmaxsecs);
741 vmu->maxsecs = vmmaxsecs;
743 vmu->maxsecs = atoi(value);
745 if (!strcasecmp(var, "maxmessage"))
746 ast_log(LOG_WARNING, "Option 'maxmessage' has been deprecated in favor of 'maxsecs'. Please make that change in your voicemail config.\n");
747 } else if (!strcasecmp(var, "maxmsg")) {
748 vmu->maxmsg = atoi(value);
749 if (vmu->maxmsg <= 0) {
750 ast_log(LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %d\n", value, MAXMSG);
751 vmu->maxmsg = MAXMSG;
752 } else if (vmu->maxmsg > MAXMSGLIMIT) {
753 ast_log(LOG_WARNING, "Maximum number of messages per folder is %d. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
754 vmu->maxmsg = MAXMSGLIMIT;
756 } else if (!strcasecmp(var, "volgain")) {
757 sscanf(value, "%lf", &vmu->volgain);
758 } else if (!strcasecmp(var, "options")) {
759 apply_options(vmu, value);
763 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
766 if (!ast_strlen_zero(vmu->uniqueid)) {
767 res = ast_update_realtime("voicemail", "uniqueid", vmu->uniqueid, "password", password, NULL);
769 ast_copy_string(vmu->password, password, sizeof(vmu->password));
779 static void apply_options(struct ast_vm_user *vmu, const char *options)
780 { /* Destructively Parse options and apply */
784 stringp = ast_strdupa(options);
785 while ((s = strsep(&stringp, "|"))) {
787 if ((var = strsep(&value, "=")) && value) {
788 apply_option(vmu, var, value);
793 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
795 struct ast_variable *tmp;
798 if (!strcasecmp(tmp->name, "vmsecret")) {
799 ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
800 } else if (!strcasecmp(tmp->name, "secret") || !strcasecmp(tmp->name, "password")) { /* don't overwrite vmsecret if it exists */
801 if (ast_strlen_zero(retval->password))
802 ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
803 } else if (!strcasecmp(tmp->name, "uniqueid")) {
804 ast_copy_string(retval->uniqueid, tmp->value, sizeof(retval->uniqueid));
805 } else if (!strcasecmp(tmp->name, "pager")) {
806 ast_copy_string(retval->pager, tmp->value, sizeof(retval->pager));
807 } else if (!strcasecmp(tmp->name, "email")) {
808 ast_copy_string(retval->email, tmp->value, sizeof(retval->email));
809 } else if (!strcasecmp(tmp->name, "fullname")) {
810 ast_copy_string(retval->fullname, tmp->value, sizeof(retval->fullname));
811 } else if (!strcasecmp(tmp->name, "context")) {
812 ast_copy_string(retval->context, tmp->value, sizeof(retval->context));
814 } else if (!strcasecmp(tmp->name, "imapuser")) {
815 ast_copy_string(retval->imapuser, tmp->value, sizeof(retval->imapuser));
816 } else if (!strcasecmp(tmp->name, "imappassword")) {
817 ast_copy_string(retval->imappassword, tmp->value, sizeof(retval->imappassword));
820 apply_option(retval, tmp->name, tmp->value);
825 static int is_valid_dtmf(const char *key)
828 char *local_key = ast_strdupa(key);
830 for (i = 0; i < strlen(key); ++i) {
831 if (!strchr(VALID_DTMF, *local_key)) {
832 ast_log(LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
840 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
842 struct ast_variable *var;
843 struct ast_vm_user *retval;
845 if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
847 ast_set_flag(retval, VM_ALLOCED);
849 memset(retval, 0, sizeof(*retval));
851 ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
852 populate_defaults(retval);
853 if (!context && ast_test_flag((&globalflags), VM_SEARCH))
854 var = ast_load_realtime("voicemail", "mailbox", mailbox, NULL);
856 var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, NULL);
858 apply_options_full(retval, var);
859 ast_variables_destroy(var);
869 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
871 /* This function could be made to generate one from a database, too */
872 struct ast_vm_user *vmu=NULL, *cur;
873 AST_LIST_LOCK(&users);
875 if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
878 AST_LIST_TRAVERSE(&users, cur, list) {
879 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
881 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
885 /* Make a copy, so that on a reload, we have no race */
886 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
887 memcpy(vmu, cur, sizeof(*vmu));
888 ast_set2_flag(vmu, !ivm, VM_ALLOCED);
889 AST_LIST_NEXT(vmu, list) = NULL;
892 vmu = find_user_realtime(ivm, context, mailbox);
893 AST_LIST_UNLOCK(&users);
897 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
899 /* This function could be made to generate one from a database, too */
900 struct ast_vm_user *cur;
902 AST_LIST_LOCK(&users);
903 AST_LIST_TRAVERSE(&users, cur, list) {
904 if ((!context || !strcasecmp(context, cur->context)) &&
905 (!strcasecmp(mailbox, cur->mailbox)))
909 ast_copy_string(cur->password, newpass, sizeof(cur->password));
912 AST_LIST_UNLOCK(&users);
916 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
918 struct ast_config *cfg=NULL;
919 struct ast_variable *var=NULL;
920 struct ast_category *cat=NULL;
921 char *category=NULL, *value=NULL, *new=NULL;
922 const char *tmp=NULL;
923 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
924 if (!change_password_realtime(vmu, newpassword))
927 /* check voicemail.conf */
928 if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags))) {
929 while ((category = ast_category_browse(cfg, category))) {
930 if (!strcasecmp(category, vmu->context)) {
931 if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
932 ast_log(LOG_WARNING, "We could not find the mailbox.\n");
935 value = strstr(tmp,",");
937 ast_log(LOG_WARNING, "variable has bad format.\n");
940 new = alloca((strlen(value)+strlen(newpassword)+1));
941 sprintf(new,"%s%s", newpassword, value);
942 if (!(cat = ast_category_get(cfg, category))) {
943 ast_log(LOG_WARNING, "Failed to get category structure.\n");
946 ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
949 /* save the results */
950 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
951 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
952 config_text_file_save(VOICEMAIL_CONFIG, cfg, "AppVoicemail");
956 /* check users.conf and update the password stored for the mailbox*/
957 /* if no vmsecret entry exists create one. */
958 if ((cfg = ast_config_load("users.conf", config_flags))) {
959 ast_debug(4, "we are looking for %s\n", vmu->mailbox);
960 while ((category = ast_category_browse(cfg, category))) {
961 ast_debug(4, "users.conf: %s\n", category);
962 if (!strcasecmp(category, vmu->mailbox)) {
963 if (!(tmp = ast_variable_retrieve(cfg, category, "vmsecret"))) {
964 ast_debug(3, "looks like we need to make vmsecret!\n");
965 var = ast_variable_new("vmsecret", newpassword, "");
967 new = alloca(strlen(newpassword)+1);
968 sprintf(new, "%s", newpassword);
969 if (!(cat = ast_category_get(cfg, category))) {
970 ast_debug(4, "failed to get category!\n");
974 ast_variable_update(cat, "vmsecret", new, NULL, 0);
976 ast_variable_append(cat, var);
979 /* save the results and clean things up */
980 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
981 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
982 config_text_file_save("users.conf", cfg, "AppVoicemail");
986 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
989 snprintf(buf,255,"%s %s %s %s",ext_pass_cmd,vmu->context,vmu->mailbox,newpassword);
990 if (!ast_safe_system(buf))
991 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
994 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
996 return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
1000 static int make_gsm_file(char *dest, size_t len, char *imapuser, char *dir, int num)
1003 if ((res = ast_mkdir(dir, 01777))) {
1004 ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dir, strerror(res));
1005 return snprintf(dest, len, "%s/msg%04d", dir, num);
1007 return snprintf(dest, len, "%s/msg%04d", dir, num);
1010 static void vm_imap_delete(int msgnum, struct vm_state *vms)
1012 unsigned long messageNum = 0;
1015 /* find real message number based on msgnum */
1016 /* this may be an index into vms->msgArray based on the msgnum. */
1018 messageNum = vms->msgArray[msgnum];
1019 if (messageNum == 0) {
1020 ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n",msgnum,messageNum);
1023 ast_debug(3, "deleting msgnum %d, which is mailbox message %lu\n",msgnum,messageNum);
1024 /* delete message */
1025 snprintf (arg, sizeof(arg), "%lu",messageNum);
1026 mail_setflag (vms->mailstream,arg,"\\DELETED");
1030 static int make_file(char *dest, int len, char *dir, int num)
1032 return snprintf(dest, len, "%s/msg%04d", dir, num);
1035 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
1036 * \param dest String. base directory.
1037 * \param len Length of dest.
1038 * \param context String. Ignored if is null or empty string.
1039 * \param ext String. Ignored if is null or empty string.
1040 * \param folder String. Ignored if is null or empty string.
1041 * \return -1 on failure, 0 on success.
1043 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
1045 mode_t mode = VOICEMAIL_DIR_MODE;
1048 make_dir(dest, len, context, ext, folder);
1049 if ((res = ast_mkdir(dest, mode))) {
1050 ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1056 /*! \brief Lock file path
1057 only return failure if ast_lock_path returns 'timeout',
1058 not if the path does not exist or any other reason
1060 static int vm_lock_path(const char *path)
1062 switch (ast_lock_path(path)) {
1063 case AST_LOCK_TIMEOUT:
1072 struct generic_prepare_struct {
1078 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
1080 struct generic_prepare_struct *gps = data;
1084 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1085 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1086 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1089 res = SQLPrepare(stmt, (unsigned char *)gps->sql, SQL_NTS);
1090 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1091 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", gps->sql);
1092 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1095 for (i = 0; i < gps->argc; i++)
1096 SQLBindParameter(stmt, i + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->argv[i]), 0, gps->argv[i], 0, NULL);
1101 static int retrieve_file(char *dir, int msgnum)
1107 void *fdm = MAP_FAILED;
1108 SQLSMALLINT colcount=0;
1115 SQLSMALLINT datatype;
1116 SQLSMALLINT decimaldigits;
1117 SQLSMALLINT nullable;
1123 char full_fn[PATH_MAX];
1125 char *argv[] = { dir, msgnums };
1126 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
1128 struct odbc_obj *obj;
1129 obj = ast_odbc_request_obj(odbc_database, 0);
1131 ast_copy_string(fmt, vmfmts, sizeof(fmt));
1132 c = strchr(fmt, '|');
1135 if (!strcasecmp(fmt, "wav49"))
1137 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
1139 make_file(fn, sizeof(fn), dir, msgnum);
1141 ast_copy_string(fn, dir, sizeof(fn));
1142 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
1144 if (!(f = fopen(full_fn, "w+"))) {
1145 ast_log(LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
1149 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
1150 snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?",odbc_table);
1151 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1153 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1154 ast_odbc_release_obj(obj);
1157 res = SQLFetch(stmt);
1158 if (res == SQL_NO_DATA) {
1159 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1160 ast_odbc_release_obj(obj);
1163 else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1164 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
1165 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1166 ast_odbc_release_obj(obj);
1169 fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
1171 ast_log(LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
1172 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1173 ast_odbc_release_obj(obj);
1176 res = SQLNumResultCols(stmt, &colcount);
1177 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1178 ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
1179 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1180 ast_odbc_release_obj(obj);
1184 fprintf(f, "[message]\n");
1185 for (x=0;x<colcount;x++) {
1187 collen = sizeof(coltitle);
1188 res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen,
1189 &datatype, &colsize, &decimaldigits, &nullable);
1190 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1191 ast_log(LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
1192 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1193 ast_odbc_release_obj(obj);
1196 if (!strcasecmp(coltitle, "recording")) {
1198 res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
1202 lseek(fd, fdlen - 1, SEEK_SET);
1203 if (write(fd, tmp, 1) != 1) {
1208 /* Read out in small chunks */
1209 for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
1210 if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
1211 ast_log(LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
1212 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1213 ast_odbc_release_obj(obj);
1216 res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
1217 munmap(fdm, CHUNKSIZE);
1218 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1219 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1221 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1222 ast_odbc_release_obj(obj);
1227 truncate(full_fn, fdlen);
1230 res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
1231 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1232 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1233 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1234 ast_odbc_release_obj(obj);
1237 if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
1238 fprintf(f, "%s=%s\n", coltitle, rowdata);
1241 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1242 ast_odbc_release_obj(obj);
1244 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1253 static int remove_file(char *dir, int msgnum)
1256 char full_fn[PATH_MAX];
1260 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
1261 make_file(fn, sizeof(fn), dir, msgnum);
1263 ast_copy_string(fn, dir, sizeof(fn));
1264 ast_filedelete(fn, NULL);
1265 if (ast_check_realtime("voicemail_data")) {
1266 ast_destroy_realtime("voicemail_data", "filename", fn, NULL);
1268 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
1273 static int last_message_index(struct ast_vm_user *vmu, char *dir)
1280 char *argv[] = { dir };
1281 struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
1283 struct odbc_obj *obj;
1284 obj = ast_odbc_request_obj(odbc_database, 0);
1286 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?",odbc_table);
1287 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1289 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1290 ast_odbc_release_obj(obj);
1293 res = SQLFetch(stmt);
1294 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1295 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
1296 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1297 ast_odbc_release_obj(obj);
1300 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
1301 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1302 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1303 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1304 ast_odbc_release_obj(obj);
1307 if (sscanf(rowdata, "%d", &x) != 1)
1308 ast_log(LOG_WARNING, "Failed to read message count!\n");
1309 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1310 ast_odbc_release_obj(obj);
1312 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1317 static int message_exists(char *dir, int msgnum)
1325 char *argv[] = { dir, msgnums };
1326 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
1328 struct odbc_obj *obj;
1329 obj = ast_odbc_request_obj(odbc_database, 0);
1331 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
1332 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?",odbc_table);
1333 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1335 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1336 ast_odbc_release_obj(obj);
1339 res = SQLFetch(stmt);
1340 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1341 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
1342 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1343 ast_odbc_release_obj(obj);
1346 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
1347 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1348 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1349 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1350 ast_odbc_release_obj(obj);
1353 if (sscanf(rowdata, "%d", &x) != 1)
1354 ast_log(LOG_WARNING, "Failed to read message count!\n");
1355 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1356 ast_odbc_release_obj(obj);
1358 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1363 static int count_messages(struct ast_vm_user *vmu, char *dir)
1365 return last_message_index(vmu, dir) + 1;
1368 static void delete_file(char *sdir, int smsg)
1373 char *argv[] = { sdir, msgnums };
1374 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
1376 struct odbc_obj *obj;
1377 obj = ast_odbc_request_obj(odbc_database, 0);
1379 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1380 snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?",odbc_table);
1381 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1383 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1385 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1386 ast_odbc_release_obj(obj);
1388 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1392 static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailboxuser, char *dmailboxcontext)
1398 struct odbc_obj *obj;
1399 char *argv[] = { ddir, msgnumd, dmailboxuser, dmailboxcontext, sdir, msgnums };
1400 struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
1402 delete_file(ddir, dmsg);
1403 obj = ast_odbc_request_obj(odbc_database, 0);
1405 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1406 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
1407 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);
1408 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1410 ast_log(LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
1412 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1413 ast_odbc_release_obj(obj);
1415 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1419 static int store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum)
1424 void *fdm = MAP_FAILED;
1431 char full_fn[PATH_MAX];
1434 const char *context="", *macrocontext="", *callerid="", *origtime="", *duration="";
1435 const char *category = "";
1436 struct ast_config *cfg=NULL;
1437 struct odbc_obj *obj;
1438 struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
1440 delete_file(dir, msgnum);
1441 obj = ast_odbc_request_obj(odbc_database, 0);
1443 ast_copy_string(fmt, vmfmts, sizeof(fmt));
1444 c = strchr(fmt, '|');
1447 if (!strcasecmp(fmt, "wav49"))
1449 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
1451 make_file(fn, sizeof(fn), dir, msgnum);
1453 ast_copy_string(fn, dir, sizeof(fn));
1454 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
1455 cfg = ast_config_load(full_fn, config_flags);
1456 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
1457 fd = open(full_fn, O_RDWR);
1459 ast_log(LOG_WARNING, "Open of sound file '%s' failed: %s\n", full_fn, strerror(errno));
1460 ast_odbc_release_obj(obj);
1464 context = ast_variable_retrieve(cfg, "message", "context");
1465 if (!context) context = "";
1466 macrocontext = ast_variable_retrieve(cfg, "message", "macrocontext");
1467 if (!macrocontext) macrocontext = "";
1468 callerid = ast_variable_retrieve(cfg, "message", "callerid");
1469 if (!callerid) callerid = "";
1470 origtime = ast_variable_retrieve(cfg, "message", "origtime");
1471 if (!origtime) origtime = "";
1472 duration = ast_variable_retrieve(cfg, "message", "duration");
1473 if (!duration) duration = "";
1474 category = ast_variable_retrieve(cfg, "message", "category");
1475 if (!category) category = "";
1477 fdlen = lseek(fd, 0, SEEK_END);
1478 lseek(fd, 0, SEEK_SET);
1479 printf("Length is %zd\n", fdlen);
1480 fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0);
1481 if (fdm == MAP_FAILED) {
1482 ast_log(LOG_WARNING, "Memory map failed!\n");
1483 ast_odbc_release_obj(obj);
1486 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1487 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1488 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1489 ast_odbc_release_obj(obj);
1492 if (!ast_strlen_zero(category))
1493 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,category) VALUES (?,?,?,?,?,?,?,?,?,?,?)",odbc_table);
1495 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext) VALUES (?,?,?,?,?,?,?,?,?,?)",odbc_table);
1496 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
1497 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1498 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1499 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1500 ast_odbc_release_obj(obj);
1503 len = fdlen; /* SQL_LEN_DATA_AT_EXEC(fdlen); */
1504 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
1505 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1506 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, fdlen, 0, (void *)fdm, fdlen, &len);
1507 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(context), 0, (void *)context, 0, NULL);
1508 SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(macrocontext), 0, (void *)macrocontext, 0, NULL);
1509 SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(callerid), 0, (void *)callerid, 0, NULL);
1510 SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(origtime), 0, (void *)origtime, 0, NULL);
1511 SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(duration), 0, (void *)duration, 0, NULL);
1512 SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxuser), 0, (void *)mailboxuser, 0, NULL);
1513 SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxcontext), 0, (void *)mailboxcontext, 0, NULL);
1514 if (!ast_strlen_zero(category))
1515 SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(category), 0, (void *)category, 0, NULL);
1516 res = ast_odbc_smart_execute(obj, stmt);
1517 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1518 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1519 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1520 ast_odbc_release_obj(obj);
1523 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1524 ast_odbc_release_obj(obj);
1526 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1529 ast_config_destroy(cfg);
1530 if (fdm != MAP_FAILED)
1537 static void rename_file(char *sdir, int smsg, char *mailboxuser, char *mailboxcontext, char *ddir, int dmsg)
1543 struct odbc_obj *obj;
1544 char *argv[] = { ddir, msgnumd, mailboxuser, mailboxcontext, sdir, msgnums };
1545 struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
1547 delete_file(ddir, dmsg);
1548 obj = ast_odbc_request_obj(odbc_database, 0);
1550 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1551 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
1552 snprintf(sql, sizeof(sql), "UPDATE %s SET dir=?, msgnum=?, mailboxuser=?, mailboxcontext=? WHERE dir=? AND msgnum=?",odbc_table);
1553 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1555 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1557 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1558 ast_odbc_release_obj(obj);
1560 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1565 #ifndef IMAP_STORAGE
1566 static int count_messages(struct ast_vm_user *vmu, char *dir)
1568 /* Find all .txt files - even if they are not in sequence from 0000 */
1572 struct dirent *vment = NULL;
1574 if (vm_lock_path(dir))
1575 return ERROR_LOCK_PATH;
1577 if ((vmdir = opendir(dir))) {
1578 while ((vment = readdir(vmdir))) {
1579 if (strlen(vment->d_name) > 7 && !strncmp(vment->d_name + 7, ".txt", 4))
1584 ast_unlock_path(dir);
1589 static void rename_file(char *sfn, char *dfn)
1591 char stxt[PATH_MAX];
1592 char dtxt[PATH_MAX];
1593 ast_filerename(sfn,dfn,NULL);
1594 snprintf(stxt, sizeof(stxt), "%s.txt", sfn);
1595 snprintf(dtxt, sizeof(dtxt), "%s.txt", dfn);
1596 if (ast_check_realtime("voicemail_data")) {
1597 ast_update_realtime("voicemail_data", "filename", sfn, "filename", dfn, NULL);
1602 static int copy(char *infile, char *outfile)
1610 #ifdef HARDLINK_WHEN_POSSIBLE
1611 /* Hard link if possible; saves disk space & is faster */
1612 if (link(infile, outfile)) {
1614 if ((ifd = open(infile, O_RDONLY)) < 0) {
1615 ast_log(LOG_WARNING, "Unable to open %s in read-only mode\n", infile);
1618 if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, VOICEMAIL_FILE_MODE)) < 0) {
1619 ast_log(LOG_WARNING, "Unable to open %s in write-only mode\n", outfile);
1624 len = read(ifd, buf, sizeof(buf));
1626 ast_log(LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
1632 res = write(ofd, buf, len);
1633 if (errno == ENOMEM || errno == ENOSPC || res != len) {
1634 ast_log(LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno));
1644 #ifdef HARDLINK_WHEN_POSSIBLE
1646 /* Hard link succeeded */
1652 static void copy_file(char *frompath, char *topath)
1654 char frompath2[PATH_MAX], topath2[PATH_MAX];
1655 struct ast_variable *tmp,*var = NULL;
1656 const char *origmailbox = NULL, *context = NULL, *macrocontext = NULL, *exten = NULL, *priority = NULL, *callerchan = NULL, *callerid = NULL, *origdate = NULL, *origtime = NULL, *category = NULL, *duration = NULL;
1657 ast_filecopy(frompath, topath, NULL);
1658 snprintf(frompath2, sizeof(frompath2), "%s.txt", frompath);
1659 snprintf(topath2, sizeof(topath2), "%s.txt", topath);
1660 if (ast_check_realtime("voicemail_data")) {
1661 var = ast_load_realtime("voicemail_data", "filename", frompath, NULL);
1662 /* This cycle converts ast_variable linked list, to va_list list of arguments, may be there is a better way to do it? */
1663 for (tmp = var; tmp; tmp = tmp->next) {
1664 if (!strcasecmp(tmp->name, "origmailbox")) {
1665 origmailbox = tmp->value;
1666 } else if (!strcasecmp(tmp->name, "context")) {
1667 context = tmp->value;
1668 } else if (!strcasecmp(tmp->name, "macrocontext")) {
1669 macrocontext = tmp->value;
1670 } else if (!strcasecmp(tmp->name, "exten")) {
1672 } else if (!strcasecmp(tmp->name, "priority")) {
1673 priority = tmp->value;
1674 } else if (!strcasecmp(tmp->name, "callerchan")) {
1675 callerchan = tmp->value;
1676 } else if (!strcasecmp(tmp->name, "callerid")) {
1677 callerid = tmp->value;
1678 } else if (!strcasecmp(tmp->name, "origdate")) {
1679 origdate = tmp->value;
1680 } else if (!strcasecmp(tmp->name, "origtime")) {
1681 origtime = tmp->value;
1682 } else if (!strcasecmp(tmp->name, "category")) {
1683 category = tmp->value;
1684 } else if (!strcasecmp(tmp->name, "duration")) {
1685 duration = tmp->value;
1688 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);
1690 copy(frompath2, topath2);
1691 ast_variables_destroy(var);
1694 * A negative return value indicates an error.
1695 * \note Should always be called with a lock already set on dir.
1697 static int last_message_index(struct ast_vm_user *vmu, char *dir)
1700 unsigned char map[MAXMSGLIMIT] = "";
1702 struct dirent *msgdirent;
1705 /* Reading the entire directory into a file map scales better than
1706 * doing a stat repeatedly on a predicted sequence. I suspect this
1707 * is partially due to stat(2) internally doing a readdir(2) itself to
1708 * find each file. */
1709 msgdir = opendir(dir);
1710 while ((msgdirent = readdir(msgdir))) {
1711 if (sscanf(msgdirent->d_name, "msg%d", &msgdirint) == 1 && msgdirint < MAXMSGLIMIT)
1716 for (x = 0; x < vmu->maxmsg; x++) {
1724 #endif /*#ifndef IMAP_STORAGE*/
1725 #endif /*#else of #ifdef ODBC_STORAGE*/
1726 #ifndef ODBC_STORAGE
1727 static int vm_delete(char *file)
1732 txtsize = (strlen(file) + 5)*sizeof(char);
1733 txt = alloca(txtsize);
1734 /* Sprintf here would safe because we alloca'd exactly the right length,
1735 * but trying to eliminate all sprintf's anyhow
1737 if (ast_check_realtime("voicemail_data")) {
1738 ast_destroy_realtime("voicemail_data", "filename", file, NULL);
1740 snprintf(txt, txtsize, "%s.txt", file);
1742 return ast_filedelete(file, NULL);
1746 static int inbuf(struct baseio *bio, FILE *fi)
1753 if ((l = fread(bio->iobuf,1,BASEMAXINLINE,fi)) <= 0) {
1767 static int inchar(struct baseio *bio, FILE *fi)
1769 if (bio->iocp>=bio->iolen) {
1770 if (!inbuf(bio, fi))
1774 return bio->iobuf[bio->iocp++];
1777 static int ochar(struct baseio *bio, int c, FILE *so)
1779 if (bio->linelength >= BASELINELEN) {
1780 if (fputs(eol,so) == EOF)
1786 if (putc(((unsigned char)c),so) == EOF)
1794 static int base_encode(char *filename, FILE *so)
1796 unsigned char dtable[BASEMAXINLINE];
1801 memset(&bio, 0, sizeof(bio));
1802 bio.iocp = BASEMAXINLINE;
1804 if (!(fi = fopen(filename, "rb"))) {
1805 ast_log(LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno));
1809 for (i= 0; i<9; i++) {
1812 dtable[26+i]= 'a'+i;
1813 dtable[26+i+9]= 'j'+i;
1815 for (i= 0; i<8; i++) {
1816 dtable[i+18]= 'S'+i;
1817 dtable[26+i+18]= 's'+i;
1819 for (i= 0; i<10; i++) {
1820 dtable[52+i]= '0'+i;
1826 unsigned char igroup[3], ogroup[4];
1829 igroup[0]= igroup[1]= igroup[2]= 0;
1831 for (n= 0;n<3;n++) {
1832 if ((c = inchar(&bio, fi)) == EOF) {
1837 igroup[n]= (unsigned char)c;
1841 ogroup[0]= dtable[igroup[0]>>2];
1842 ogroup[1]= dtable[((igroup[0]&3)<<4) | (igroup[1]>>4)];
1843 ogroup[2]= dtable[((igroup[1]&0xF)<<2) | (igroup[2]>>6)];
1844 ogroup[3]= dtable[igroup[2]&0x3F];
1854 ochar(&bio, ogroup[i], so);
1860 if (fputs(eol,so)==EOF)
1866 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)
1869 /* Prepare variables for substitution in email body and subject */
1870 pbx_builtin_setvar_helper(ast, "VM_NAME", vmu->fullname);
1871 pbx_builtin_setvar_helper(ast, "VM_DUR", dur);
1872 snprintf(passdata, passdatasize, "%d", msgnum);
1873 pbx_builtin_setvar_helper(ast, "VM_MSGNUM", passdata);
1874 pbx_builtin_setvar_helper(ast, "VM_CONTEXT", context);
1875 pbx_builtin_setvar_helper(ast, "VM_MAILBOX", mailbox);
1876 pbx_builtin_setvar_helper(ast, "VM_CALLERID", ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, "Unknown Caller"));
1877 pbx_builtin_setvar_helper(ast, "VM_CIDNAME", (cidname ? cidname : "an unknown caller"));
1878 pbx_builtin_setvar_helper(ast, "VM_CIDNUM", (cidnum ? cidnum : "an unknown caller"));
1879 pbx_builtin_setvar_helper(ast, "VM_DATE", date);
1880 pbx_builtin_setvar_helper(ast, "VM_CATEGORY", category ? ast_strdupa(category) : "no category");
1883 static char *quote(const char *from, char *to, size_t len)
1887 for (; ptr < to + len - 1; from++) {
1890 else if (*from == '\0')
1894 if (ptr < to + len - 1)
1901 * fill in *tm for current time according to the proper timezone, if any.
1902 * Return tm so it can be used as a function argument.
1904 static const struct ast_tm *vmu_tm(const struct ast_vm_user *vmu, struct ast_tm *tm)
1906 const struct vm_zone *z = NULL;
1907 struct timeval t = ast_tvnow();
1909 /* Does this user have a timezone specified? */
1910 if (!ast_strlen_zero(vmu->zonetag)) {
1911 /* Find the zone in the list */
1912 AST_LIST_LOCK(&zones);
1913 AST_LIST_TRAVERSE(&zones, z, list) {
1914 if (!strcmp(z->name, vmu->zonetag))
1917 AST_LIST_UNLOCK(&zones);
1919 ast_localtime(&t, tm, z ? z->timezone : NULL);
1923 /*! \brief same as mkstemp, but return a FILE * */
1924 static FILE *vm_mkftemp(char *template)
1927 int pfd = mkstemp(template);
1928 chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
1930 p = fdopen(pfd, "w+");
1939 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)
1942 char host[MAXHOSTNAMELEN] = "";
1950 size_t len_passdata;
1951 char *greeting_attachment;
1959 gethostname(host, sizeof(host)-1);
1961 if (strchr(srcemail, '@'))
1962 ast_copy_string(who, srcemail, sizeof(who));
1964 snprintf(who, sizeof(who), "%s@%s", srcemail, host);
1966 greeting_attachment = strrchr(ast_strdupa(attach), '/');
1967 if (greeting_attachment)
1968 *greeting_attachment++ = '\0';
1970 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
1971 ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
1972 fprintf(p, "Date: %s" ENDL, date);
1974 /* Set date format for voicemail mail */
1975 ast_strftime(date, sizeof(date), emaildateformat, &tm);
1977 if (!ast_strlen_zero(fromstring)) {
1978 struct ast_channel *ast;
1979 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
1981 int vmlen = strlen(fromstring)*3 + 200;
1982 passdata = alloca(vmlen);
1983 memset(passdata, 0, vmlen);
1984 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
1985 pbx_substitute_variables_helper(ast, fromstring, passdata, vmlen);
1986 len_passdata = strlen(passdata) * 2 + 3;
1987 passdata2 = alloca(len_passdata);
1988 fprintf(p, "From: %s <%s>" ENDL, quote(passdata, passdata2, len_passdata), who);
1989 ast_channel_free(ast);
1991 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
1993 fprintf(p, "From: Asterisk PBX <%s>" ENDL, who);
1994 len_passdata = strlen(vmu->fullname) * 2 + 3;
1995 passdata2 = alloca(len_passdata);
1996 fprintf(p, "To: %s <%s>" ENDL, quote(vmu->fullname, passdata2, len_passdata), vmu->email);
1997 if (!ast_strlen_zero(emailsubject)) {
1998 struct ast_channel *ast;
1999 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2001 int vmlen = strlen(emailsubject) * 3 + 200;
2002 passdata = alloca(vmlen);
2003 memset(passdata, 0, vmlen);
2004 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2005 pbx_substitute_variables_helper(ast, emailsubject, passdata, vmlen);
2006 fprintf(p, "Subject: %s" ENDL, passdata);
2007 ast_channel_free(ast);
2009 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2010 } else if (!ast_strlen_zero(emailtitle)) {
2011 fprintf(p, emailtitle, msgnum + 1, mailbox) ;
2013 } else if (ast_test_flag((&globalflags), VM_PBXSKIP))
2014 fprintf(p, "Subject: New message %d in mailbox %s" ENDL, msgnum + 1, mailbox);
2016 fprintf(p, "Subject: [PBX]: New message %d in mailbox %s" ENDL, msgnum + 1, mailbox);
2017 fprintf(p, "Message-ID: <Asterisk-%d-%d-%s-%d@%s>" ENDL, msgnum + 1, (unsigned int)ast_random(), mailbox, (int)getpid(), host);
2019 /* additional information needed for IMAP searching */
2020 fprintf(p, "X-Asterisk-VM-Message-Num: %d" ENDL, msgnum + 1);
2021 /* fprintf(p, "X-Asterisk-VM-Orig-Mailbox: %s" ENDL, ext); */
2022 fprintf(p, "X-Asterisk-VM-Server-Name: %s" ENDL, fromstring);
2023 fprintf(p, "X-Asterisk-VM-Context: %s" ENDL, context);
2024 fprintf(p, "X-Asterisk-VM-Extension: %s" ENDL, mailbox);
2025 fprintf(p, "X-Asterisk-VM-Priority: %d" ENDL, chan->priority);
2026 fprintf(p, "X-Asterisk-VM-Caller-channel: %s" ENDL, chan->name);
2027 fprintf(p, "X-Asterisk-VM-Caller-ID-Num: %s" ENDL, cidnum);
2028 fprintf(p, "X-Asterisk-VM-Caller-ID-Name: %s" ENDL, cidname);
2029 fprintf(p, "X-Asterisk-VM-Duration: %d" ENDL, duration);
2030 if (!ast_strlen_zero(category))
2031 fprintf(p, "X-Asterisk-VM-Category: %s" ENDL, category);
2032 fprintf(p, "X-Asterisk-VM-Message-Type: %s\n", msgnum > -1 ? "Message" : greeting_attachment);
2033 fprintf(p, "X-Asterisk-VM-Orig-date: %s" ENDL, date);
2034 fprintf(p, "X-Asterisk-VM-Orig-time: %ld" ENDL, (long)time(NULL));
2036 if (!ast_strlen_zero(cidnum))
2037 fprintf(p, "X-Asterisk-CallerID: %s" ENDL, cidnum);
2038 if (!ast_strlen_zero(cidname))
2039 fprintf(p, "X-Asterisk-CallerIDName: %s" ENDL, cidname);
2040 fprintf(p, "MIME-Version: 1.0" ENDL);
2041 if (attach_user_voicemail) {
2042 /* Something unique. */
2043 snprintf(bound, sizeof(bound), "----voicemail_%d%s%d%d", msgnum + 1, mailbox, (int)getpid(), (unsigned int)ast_random());
2045 fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"" ENDL, bound);
2046 fprintf(p, ENDL ENDL "This is a multi-part message in MIME format." ENDL ENDL);
2047 fprintf(p, "--%s" ENDL, bound);
2049 fprintf(p, "Content-Type: text/plain; charset=%s" ENDL "Content-Transfer-Encoding: 8bit" ENDL ENDL, charset);
2051 struct ast_channel *ast;
2052 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2054 int vmlen = strlen(emailbody)*3 + 200;
2055 passdata = alloca(vmlen);
2056 memset(passdata, 0, vmlen);
2057 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2058 pbx_substitute_variables_helper(ast, emailbody, passdata, vmlen);
2059 fprintf(p, "%s" ENDL, passdata);
2060 ast_channel_free(ast);
2062 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2063 } else if (msgnum > -1){
2064 fprintf(p, "Dear %s:" ENDL ENDL "\tJust wanted to let you know you were just left a %s long message (number %d)" ENDL
2066 "in mailbox %s from %s, on %s so you might" ENDL
2067 "want to check it when you get a chance. Thanks!" ENDL ENDL "\t\t\t\t--Asterisk" ENDL ENDL, vmu->fullname,
2068 dur, msgnum + 1, mailbox, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
2070 fprintf(p, "This message is to let you know that your greeting was changed on %s." ENDL
2071 "Please do not delete this message, lest your greeting vanish with it." ENDL ENDL, date);
2073 if (attach_user_voicemail) {
2074 /* Eww. We want formats to tell us their own MIME type */
2075 char *ctype = (!strcasecmp(format, "ogg")) ? "application/" : "audio/x-";
2076 char tmpdir[256], newtmp[256];
2079 if (vmu->volgain < -.001 || vmu->volgain > .001) {
2080 create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, vmu->mailbox, "tmp");
2081 snprintf(newtmp, sizeof(newtmp), "%s/XXXXXX", tmpdir);
2082 tmpfd = mkstemp(newtmp);
2083 chmod(newtmp, VOICEMAIL_FILE_MODE & ~my_umask);
2084 ast_debug(3, "newtmp: %s\n", newtmp);
2086 snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, attach, format, newtmp, format);
2087 ast_safe_system(tmpcmd);
2089 ast_debug(3, "VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", attach, format, vmu->volgain, mailbox);
2092 fprintf(p, "--%s" ENDL, bound);
2094 fprintf(p, "Content-Type: %s%s; name=\"msg%04d.%s\"" ENDL, ctype, format, msgnum + 1, format);
2096 fprintf(p, "Content-Type: %s%s; name=\"%s.%s\"" ENDL, ctype, format, greeting_attachment, format);
2097 fprintf(p, "Content-Transfer-Encoding: base64" ENDL);
2098 fprintf(p, "Content-Description: Voicemail sound attachment." ENDL);
2100 fprintf(p, "Content-Disposition: attachment; filename=\"msg%04d.%s\"" ENDL ENDL, msgnum + 1, format);
2102 fprintf(p, "Content-Disposition: attachment; filename=\"%s.%s\"" ENDL ENDL, greeting_attachment, format);
2103 snprintf(fname, sizeof(fname), "%s.%s", attach, format);
2104 base_encode(fname, p);
2105 fprintf(p, ENDL "--%s--" ENDL "." ENDL, bound);
2115 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)
2118 char tmp[80] = "/tmp/astmail-XXXXXX";
2121 if (vmu && ast_strlen_zero(vmu->email)) {
2122 ast_log(LOG_WARNING, "E-mail address missing for mailbox [%s]. E-mail will not be sent.\n", vmu->mailbox);
2125 if (!strcmp(format, "wav49"))
2127 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));
2128 /* Make a temporary file instead of piping directly to sendmail, in case the mail
2130 if ((p = vm_mkftemp(tmp)) == NULL) {
2131 ast_log(LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
2134 make_email_file(p, srcemail, vmu, msgnum, context, mailbox, cidnum, cidname, attach, format, duration, attach_user_voicemail, chan, category, 0);
2136 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
2137 ast_safe_system(tmp2);
2138 ast_debug(1, "Sent mail to %s with command '%s'\n", vmu->email, mailcmd);
2143 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)
2146 char host[MAXHOSTNAMELEN] = "";
2149 char tmp[80] = "/tmp/astmail-XXXXXX";
2150 char tmp2[PATH_MAX];
2154 if ((p = vm_mkftemp(tmp)) == NULL) {
2155 ast_log(LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
2158 gethostname(host, sizeof(host)-1);
2159 if (strchr(srcemail, '@'))
2160 ast_copy_string(who, srcemail, sizeof(who));
2162 snprintf(who, sizeof(who), "%s@%s", srcemail, host);
2163 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
2164 ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
2165 fprintf(p, "Date: %s\n", date);
2167 if (*pagerfromstring) {
2168 struct ast_channel *ast;
2169 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2171 int vmlen = strlen(fromstring)*3 + 200;
2172 passdata = alloca(vmlen);
2173 memset(passdata, 0, vmlen);
2174 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2175 pbx_substitute_variables_helper(ast, pagerfromstring, passdata, vmlen);
2176 fprintf(p, "From: %s <%s>\n", passdata, who);
2177 ast_channel_free(ast);
2179 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2181 fprintf(p, "From: Asterisk PBX <%s>\n", who);
2182 fprintf(p, "To: %s\n", pager);
2184 struct ast_channel *ast;
2185 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2187 int vmlen = strlen(pagersubject) * 3 + 200;
2188 passdata = alloca(vmlen);
2189 memset(passdata, 0, vmlen);
2190 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2191 pbx_substitute_variables_helper(ast, pagersubject, passdata, vmlen);
2192 fprintf(p, "Subject: %s\n\n", passdata);
2193 ast_channel_free(ast);
2195 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2197 fprintf(p, "Subject: New VM\n\n");
2199 ast_strftime(date, sizeof(date), "%A, %B %d, %Y at %r", &tm);
2201 struct ast_channel *ast;
2202 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2204 int vmlen = strlen(pagerbody) * 3 + 200;
2205 passdata = alloca(vmlen);
2206 memset(passdata, 0, vmlen);
2207 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2208 pbx_substitute_variables_helper(ast, pagerbody, passdata, vmlen);
2209 fprintf(p, "%s\n", passdata);
2210 ast_channel_free(ast);
2212 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2214 fprintf(p, "New %s long msg in box %s\n"
2215 "from %s, on %s", dur, mailbox, (cidname ? cidname : (cidnum ? cidnum : "unknown")), date);
2218 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
2219 ast_safe_system(tmp2);
2220 ast_debug(1, "Sent page to %s with command '%s'\n", pager, mailcmd);
2224 static int get_date(char *s, int len)
2227 struct timeval t = ast_tvnow();
2229 ast_localtime(&t, &tm, "UTC");
2231 return ast_strftime(s, len, "%a %b %e %r UTC %Y", &tm);
2234 static int invent_message(struct ast_channel *chan, char *context, char *ext, int busy, char *ecodes)
2238 char dest[PATH_MAX];
2240 snprintf(fn, sizeof(fn), "%s%s/%s/greet", VM_SPOOL_DIR, context, ext);
2242 if ((res = create_dirpath(dest, sizeof(dest), context, ext, ""))) {
2243 ast_log(LOG_WARNING, "Failed to make directory(%s)\n", fn);
2247 RETRIEVE(fn, -1, ext, context);
2248 if (ast_fileexists(fn, NULL, NULL) > 0) {
2249 res = ast_stream_and_wait(chan, fn, ecodes);
2255 /* Dispose just in case */
2257 res = ast_stream_and_wait(chan, "vm-theperson", ecodes);
2260 res = ast_say_digit_str(chan, ext, ecodes, chan->language);
2264 res = ast_stream_and_wait(chan, busy ? "vm-isonphone" : "vm-isunavail", ecodes);
2268 static void free_user(struct ast_vm_user *vmu)
2270 if (!ast_test_flag(vmu, VM_ALLOCED))
2276 static void free_zone(struct vm_zone *z)
2281 static const char *mbox(int id)
2283 static const char *msgs[] = {
2299 return (id >= 0 && id < (sizeof(msgs)/sizeof(msgs[0]))) ? msgs[id] : "Unknown";
2302 static int folder_int(const char *folder)
2304 /*assume a NULL folder means INBOX*/
2308 if (!strcasecmp(folder, imapfolder))
2310 if (!strcasecmp(folder, "INBOX"))
2313 else if (!strcasecmp(folder, "Old"))
2315 else if (!strcasecmp(folder, "Work"))
2317 else if (!strcasecmp(folder, "Family"))
2319 else if (!strcasecmp(folder, "Friends"))
2321 else if (!strcasecmp(folder, "Cust1"))
2323 else if (!strcasecmp(folder, "Cust2"))
2325 else if (!strcasecmp(folder, "Cust3"))
2327 else if (!strcasecmp(folder, "Cust4"))
2329 else if (!strcasecmp(folder, "Cust5"))
2331 else /*assume they meant INBOX if folder is not found otherwise*/
2337 /*! XXX \todo Fix this function to support multiple mailboxes in the intput string */
2338 static int inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
2345 char tmp[PATH_MAX] = "";
2346 struct odbc_obj *obj;
2348 struct generic_prepare_struct gps = { .sql = sql, .argc = 0 };
2355 /* If no mailbox, return immediately */
2356 if (ast_strlen_zero(mailbox))
2359 ast_copy_string(tmp, mailbox, sizeof(tmp));
2361 context = strchr(tmp, '@');
2366 context = "default";
2368 obj = ast_odbc_request_obj(odbc_database, 0);
2370 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "INBOX");
2371 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2373 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2374 ast_odbc_release_obj(obj);
2377 res = SQLFetch(stmt);
2378 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2379 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2380 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2381 ast_odbc_release_obj(obj);
2384 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2385 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2386 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2387 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2388 ast_odbc_release_obj(obj);
2391 *newmsgs = atoi(rowdata);
2392 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2394 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "Old");
2395 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2397 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2398 ast_odbc_release_obj(obj);
2401 res = SQLFetch(stmt);
2402 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2403 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2404 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2405 ast_odbc_release_obj(obj);
2408 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2409 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2410 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2411 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2412 ast_odbc_release_obj(obj);
2415 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2416 ast_odbc_release_obj(obj);
2417 *oldmsgs = atoi(rowdata);
2420 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2426 static int messagecount(const char *context, const char *mailbox, const char *folder)
2428 struct odbc_obj *obj = NULL;
2431 SQLHSTMT stmt = NULL;
2434 struct generic_prepare_struct gps = { .sql = sql, .argc = 0 };
2437 /* If no mailbox, return immediately */
2438 if (ast_strlen_zero(mailbox))
2441 obj = ast_odbc_request_obj(odbc_database, 0);
2443 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, mailbox, folder);
2444 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2446 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2449 res = SQLFetch(stmt);
2450 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2451 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2452 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2455 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2456 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2457 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2458 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2461 nummsgs = atoi(rowdata);
2462 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2464 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2468 ast_odbc_release_obj(obj);
2472 static int has_voicemail(const char *mailbox, const char *folder)
2474 char tmp[256], *tmp2 = tmp, *mbox, *context;
2475 ast_copy_string(tmp, mailbox, sizeof(tmp));
2476 while ((context = mbox = strsep(&tmp2, ","))) {
2477 strsep(&context, "@");
2478 if (ast_strlen_zero(context))
2479 context = "default";
2480 if (messagecount(context, mbox, folder))
2486 #elif defined(IMAP_STORAGE)
2488 static int imap_store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms)
2490 char *myserveremail = serveremail;
2495 char tmp[80] = "/tmp/astmail-XXXXXX";
2501 /* Attach only the first format */
2502 fmt = ast_strdupa(fmt);
2504 strsep(&stringp, "|");
2506 if (!ast_strlen_zero(vmu->serveremail))
2507 myserveremail = vmu->serveremail;
2510 make_file(fn, sizeof(fn), dir, msgnum);
2512 ast_copy_string (fn, dir, sizeof(fn));
2514 if (ast_strlen_zero(vmu->email)) {
2515 /*we need the vmu->email to be set when we call make_email_file, but if we keep it set,
2516 * a duplicate e-mail will be created. So at the end of this function, we will revert back to an empty
2517 * string if tempcopy is 1
2519 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
2523 if (!strcmp(fmt, "wav49"))
2525 ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
2527 /* Make a temporary file instead of piping directly to sendmail, in case the mail
2529 if (!(p = vm_mkftemp(tmp))) {
2530 ast_log(LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
2532 *(vmu->email) = '\0';
2536 if (msgnum < 0 && imapgreetings) {
2537 init_mailstream(vms, GREETINGS_FOLDER);
2538 imap_delete_old_greeting(fn, vms);
2541 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);
2542 /* read mail file to memory */
2545 if (!(buf = ast_malloc(len+1))) {
2546 ast_log(LOG_ERROR, "Can't allocate %ld bytes to read message\n", len+1);
2549 *(vmu->email) = '\0';
2552 fread(buf, len, 1, p);
2553 ((char *)buf)[len] = '\0';
2554 INIT(&str, mail_string, buf, len);
2555 init_mailstream(vms, NEW_FOLDER);
2556 imap_mailbox_name(mailbox, sizeof(mailbox), vms, NEW_FOLDER, 1);
2557 if (!mail_append(vms->mailstream, mailbox, &str))
2558 ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
2562 ast_debug(3, "%s stored\n", fn);
2565 *(vmu->email) = '\0';
2571 static int messagecount(const char *context, const char *mailbox, const char *folder)
2576 struct ast_vm_user *vmu, vmus;
2577 struct vm_state *vms_p;
2579 int fold = folder_int(folder);
2581 if (ast_strlen_zero(mailbox))
2584 /* We have to get the user before we can open the stream! */
2585 /* ast_log (LOG_DEBUG,"Before find_user, context is %s and mailbox is %s\n",context,mailbox); */
2586 vmu = find_user(&vmus, context, mailbox);
2588 ast_log (LOG_ERROR,"Couldn't find mailbox %s in context %s\n",mailbox,context);
2591 /* No IMAP account available */
2592 if (vmu->imapuser[0] == '\0') {
2593 ast_log (LOG_WARNING,"IMAP user not set for mailbox %s\n",vmu->mailbox);
2598 /* No IMAP account available */
2599 if (vmu->imapuser[0] == '\0') {
2600 ast_log (LOG_WARNING,"IMAP user not set for mailbox %s\n",vmu->mailbox);
2605 /* check if someone is accessing this box right now... */
2606 vms_p = get_vm_state_by_imapuser(vmu->imapuser,1);
2608 vms_p = get_vm_state_by_mailbox(mailbox,1);
2611 ast_debug(3, "Returning before search - user is logged in\n");
2612 if (fold == 0) {/*INBOX*/
2613 return vms_p->newmessages;
2615 if (fold == 1) {/*Old messages*/
2616 return vms_p->oldmessages;
2620 /* add one if not there... */
2621 vms_p = get_vm_state_by_imapuser(vmu->imapuser,0);
2623 vms_p = get_vm_state_by_mailbox(mailbox,0);
2627 ast_debug(3,"Adding new vmstate for %s\n",vmu->imapuser);
2628 if (!(vms_p = ast_calloc(1, sizeof(*vms_p)))) {
2631 ast_copy_string(vms_p->imapuser,vmu->imapuser, sizeof(vms_p->imapuser));
2632 ast_copy_string(vms_p->username, mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
2633 vms_p->mailstream = NIL; /* save for access from interactive entry point */
2634 ast_debug(3, "Copied %s to %s\n",vmu->imapuser,vms_p->imapuser);
2636 /* set mailbox to INBOX! */
2637 ast_copy_string(vms_p->curbox, mbox(fold), sizeof(vms_p->curbox));
2638 init_vm_state(vms_p);
2639 vmstate_insert(vms_p);
2641 ret = init_mailstream(vms_p, fold);
2642 if (!vms_p->mailstream) {
2643 ast_log (LOG_ERROR,"Houston we have a problem - IMAP mailstream is NULL\n");
2647 pgm = mail_newsearchpgm ();
2648 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)mailbox);
2654 /* In the special case where fold is 1 (old messages) we have to do things a bit
2655 * differently. Old messages are stored in the INBOX but are marked as "seen"
2664 vms_p->vmArrayIndex = 0;
2665 mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
2667 vms_p->newmessages = vms_p->vmArrayIndex;
2669 vms_p->oldmessages = vms_p->vmArrayIndex;
2670 /*Freeing the searchpgm also frees the searchhdr*/
2671 mail_free_searchpgm(&pgm);
2673 return vms_p->vmArrayIndex;
2675 mail_ping(vms_p->mailstream);
2679 static int inboxcount(const char *mailbox_context, int *newmsgs, int *oldmsgs)
2681 char tmp[PATH_MAX] = "";
2691 ast_debug(3,"Mailbox is set to %s\n",mailbox_context);
2692 /* If no mailbox, return immediately */
2693 if (ast_strlen_zero(mailbox_context))
2696 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2697 context = strchr(tmp, '@');
2698 if (strchr(mailbox_context, ',')) {
2700 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2702 while ((cur = strsep(&mb, ", "))) {
2703 if (!ast_strlen_zero(cur)) {
2704 if (inboxcount(cur, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2721 context = "default";
2722 mailboxnc = (char *)mailbox_context;
2725 if ((*newmsgs = messagecount(context, mailboxnc, imapfolder)) < 0)
2729 if ((*oldmsgs = messagecount(context, mailboxnc, "Old")) < 0)
2736 static int has_voicemail(const char *mailbox, const char *folder)
2738 char tmp[256], *tmp2, *mbox, *context;
2739 ast_copy_string(tmp, mailbox, sizeof(tmp));
2741 if (strchr(tmp2, ',')) {
2742 while ((mbox = strsep(&tmp2, ","))) {
2743 if (!ast_strlen_zero(mbox)) {
2744 if (has_voicemail(mbox, folder))
2749 if ((context= strchr(tmp, '@')))
2752 context = "default";
2753 return messagecount(context, tmp, folder) ? 1 : 0;
2756 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)
2758 struct vm_state *sendvms = NULL, *destvms = NULL;
2759 char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
2760 if (!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 2))) {
2761 ast_log(LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
2764 if (!(destvms = get_vm_state_by_imapuser(recip->imapuser, 2))) {
2765 ast_log(LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
2768 snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
2769 if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(imbox)) == T))
2771 ast_log(LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
2776 #ifndef IMAP_STORAGE
2777 /* copy message only used by file storage */
2778 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)
2780 char fromdir[PATH_MAX], todir[PATH_MAX], frompath[PATH_MAX], topath[PATH_MAX];
2781 const char *frombox = mbox(imbox);
2784 ast_log(LOG_NOTICE, "Copying message from %s@%s to %s@%s\n", vmu->mailbox, vmu->context, recip->mailbox, recip->context);
2786 create_dirpath(todir, sizeof(todir), recip->context, recip->mailbox, "INBOX");
2789 make_dir(fromdir, sizeof(fromdir), vmu->context, vmu->mailbox, frombox);
2791 ast_copy_string(fromdir, dir, sizeof(fromdir));
2793 make_file(frompath, sizeof(frompath), fromdir, msgnum);
2794 make_dir(todir, sizeof(todir), recip->context, recip->mailbox, frombox);
2796 if (vm_lock_path(todir))
2797 return ERROR_LOCK_PATH;
2799 recipmsgnum = last_message_index(recip, todir) + 1;
2800 if (recipmsgnum < recip->maxmsg) {
2801 make_file(topath, sizeof(topath), todir, recipmsgnum);
2802 COPY(fromdir, msgnum, todir, recipmsgnum, recip->mailbox, recip->context, frompath, topath);
2804 ast_log(LOG_ERROR, "Recipient mailbox %s@%s is full\n", recip->mailbox, recip->context);
2806 ast_unlock_path(todir);
2807 notify_new_message(chan, recip, recipmsgnum, duration, fmt, S_OR(chan->cid.cid_num, NULL), S_OR(chan->cid.cid_name, NULL));
2812 #if !(defined(IMAP_STORAGE) || defined(ODBC_STORAGE))
2813 static int messagecount(const char *context, const char *mailbox, const char *folder)
2815 return __has_voicemail(context, mailbox, folder, 0);
2819 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit)
2826 /* If no mailbox, return immediately */
2827 if (ast_strlen_zero(mailbox))
2830 if (ast_strlen_zero(folder))
2832 if (ast_strlen_zero(context))
2833 context = "default";
2835 snprintf(fn, sizeof(fn), "%s%s/%s/%s", VM_SPOOL_DIR, context, mailbox, folder);
2837 if (!(dir = opendir(fn)))
2840 while ((de = readdir(dir))) {
2841 if (!strncasecmp(de->d_name, "msg", 3)) {
2845 } else if (!strncasecmp(de->d_name + 8, "txt", 3))
2856 static int has_voicemail(const char *mailbox, const char *folder)
2858 char tmp[256], *tmp2 = tmp, *mbox, *context;
2859 ast_copy_string(tmp, mailbox, sizeof(tmp));
2860 while ((mbox = strsep(&tmp2, ","))) {
2861 if ((context = strchr(mbox, '@')))
2864 context = "default";
2865 if (__has_voicemail(context, mbox, folder, 1))
2872 static int inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
2877 /* If no mailbox, return immediately */
2878 if (ast_strlen_zero(mailbox))
2886 if (strchr(mailbox, ',')) {
2890 ast_copy_string(tmp, mailbox, sizeof(tmp));
2892 while ((cur = strsep(&mb, ", "))) {
2893 if (!ast_strlen_zero(cur)) {
2894 if (inboxcount(cur, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2907 ast_copy_string(tmp, mailbox, sizeof(tmp));
2909 if ((context = strchr(tmp, '@')))
2912 context = "default";
2915 *newmsgs = __has_voicemail(context, tmp, "INBOX", 0);
2917 *oldmsgs = __has_voicemail(context, tmp, "Old", 0);
2924 static void run_externnotify(char *context, char *extension)
2926 char arguments[255];
2927 char ext_context[256] = "";
2928 int newvoicemails = 0, oldvoicemails = 0;
2929 struct ast_smdi_mwi_message *mwi_msg;
2931 if (!ast_strlen_zero(context))
2932 snprintf(ext_context, sizeof(ext_context), "%s@%s", extension, context);
2934 ast_copy_string(ext_context, extension, sizeof(ext_context));
2937 if (ast_app_has_voicemail(ext_context, NULL))
2938 ast_smdi_mwi_set(smdi_iface, extension);
2940 ast_smdi_mwi_unset(smdi_iface, extension);
2942 if ((mwi_msg = ast_smdi_mwi_message_wait(smdi_iface, SMDI_MWI_WAIT_TIMEOUT))) {
2943 ast_log(LOG_ERROR, "Error executing SMDI MWI change for %s on %s\n", extension, smdi_iface->name);
2944 if (!strncmp(mwi_msg->cause, "INV", 3))
2945 ast_log(LOG_ERROR, "Invalid MWI extension: %s\n", mwi_msg->fwd_st);
2946 else if (!strncmp(mwi_msg->cause, "BLK", 3))
2947 ast_log(LOG_WARNING, "MWI light was already on or off for %s\n", mwi_msg->fwd_st);
2948 ast_log(LOG_WARNING, "The switch reported '%s'\n", mwi_msg->cause);
2949 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
2951 ast_debug(1, "Successfully executed SMDI MWI change for %s on %s\n", extension, smdi_iface->name);
2955 if (!ast_strlen_zero(externnotify)) {
2956 if (inboxcount(ext_context, &newvoicemails, &oldvoicemails)) {
2957 ast_log(LOG_ERROR, "Problem in calculating number of voicemail messages available for extension %s\n", extension);
2959 snprintf(arguments, sizeof(arguments), "%s %s %s %d&", externnotify, context, extension, newvoicemails);
2960 ast_debug(1, "Executing %s\n", arguments);
2961 ast_safe_system(arguments);
2966 struct leave_vm_options {
2968 signed char record_gain;
2971 static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_options *options)
2974 int newmsgs, oldmsgs;
2975 struct vm_state *vms = NULL;
2977 char txtfile[PATH_MAX], tmptxtfile[PATH_MAX];
2993 char dir[PATH_MAX], tmpdir[PATH_MAX];
2995 char prefile[PATH_MAX] = "";
2996 char tempfile[PATH_MAX] = "";
2997 char ext_context[256] = "";
3000 char ecodes[16] = "#";
3001 char tmp[1024] = "", *tmpptr;
3002 struct ast_vm_user *vmu;
3003 struct ast_vm_user svm;
3004 const char *category = NULL;
3006 ast_copy_string(tmp, ext, sizeof(tmp));
3008 if ((context = strchr(tmp, '@'))) {
3010 tmpptr = strchr(context, '&');
3012 tmpptr = strchr(ext, '&');
3018 category = pbx_builtin_getvar_helper(chan, "VM_CATEGORY");
3020 ast_debug(3, "Before find_user\n");
3021 if (!(vmu = find_user(&svm, context, ext))) {
3022 ast_log(LOG_WARNING, "No entry in voicemail config file for '%s'\n", ext);
3023 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
3026 /* Setup pre-file if appropriate */
3027 if (strcmp(vmu->context, "default"))
3028 snprintf(ext_context, sizeof(ext_context), "%s@%s", ext, vmu->context);
3030 ast_copy_string(ext_context, vmu->mailbox, sizeof(ext_context));
3031 if (ast_test_flag(options, OPT_BUSY_GREETING)) {
3032 snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, ext);
3033 } else if (ast_test_flag(options, OPT_UNAVAIL_GREETING)) {
3034 snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, ext);
3036 snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", VM_SPOOL_DIR, vmu->context, ext);
3037 if ((res = create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, ext, "tmp"))) {
3038 ast_log(LOG_WARNING, "Failed to make directory (%s)\n", tempfile);
3041 RETRIEVE(tempfile, -1, ext, context);
3042 if (ast_fileexists(tempfile, NULL, NULL) > 0)
3043 ast_copy_string(prefile, tempfile, sizeof(prefile));
3044 DISPOSE(tempfile, -1);
3045 /* It's easier just to try to make it than to check for its existence */
3046 create_dirpath(dir, sizeof(dir), vmu->context, ext, "INBOX");
3048 /* Check current or macro-calling context for special extensions */
3049 if (ast_test_flag(vmu, VM_OPERATOR)) {
3050 if (!ast_strlen_zero(vmu->exit)) {
3051 if (ast_exists_extension(chan, vmu->exit, "o", 1, chan->cid.cid_num)) {
3052 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
3055 } else if (ast_exists_extension(chan, chan->context, "o", 1, chan->cid.cid_num)) {
3056 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
3059 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num)) {
3060 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
3065 if (!ast_strlen_zero(vmu->exit)) {
3066 if (ast_exists_extension(chan, vmu->exit, "a", 1, chan->cid.cid_num))
3067 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
3068 } else if (ast_exists_extension(chan, chan->context, "a", 1, chan->cid.cid_num))
3069 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
3070 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "a", 1, chan->cid.cid_num)) {
3071 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
3075 /* Play the beginning intro if desired */
3076 if (!ast_strlen_zero(prefile)) {
3080 RETRIEVE(prefile, -1, ext, context);
3081 if (ast_fileexists(prefile, NULL, NULL) > 0) {
3082 if (ast_streamfile(chan, prefile, chan->language) > -1)
3083 res = ast_waitstream(chan, ecodes);
3085 if (success == -1) {
3086 /* We couldn't retrieve the file from the database, but we found it on the file system. Let's put it in the database. */
3087 ast_debug(1, "Greeting not retrieved from database, but found in file storage. Inserting into database\n");
3088 store_file(prefile, vmu->mailbox, vmu->context, -1);
3092 ast_debug(1, "%s doesn't exist, doing what we can\n", prefile);
3093 res = invent_message(chan, vmu->context, ext, ast_test_flag(options, OPT_BUSY_GREETING), ecodes);
3095 DISPOSE(prefile, -1);
3097 ast_debug(1, "Hang up during prefile playback\n");
3099 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");