Newer
Older
* Asterisk -- An open source telephony toolkit.
* Copyright (C) 1999 - 2006, Digium, Inc.
* Mark Spencer <markster@digium.com>
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
*
* \brief Comedian Mail - Voicemail System
*
* \author Mark Spencer <markster@digium.com>
*
* \extref Unixodbc - http://www.unixodbc.org
* \extref A source distribution of University of Washington's IMAP
c-client (http://www.washington.edu/imap/
*
* \note For information about voicemail IMAP storage, read doc/imapstorage.txt
* \note This module requires res_adsi to load. This needs to be optional
* during compilation.
*
*
*
* \note This file is now almost impossible to work with, due to all \#ifdefs.
* Feels like the database code before realtime. Someone - please come up
* with a plan to clean this up.
Russell Bryant
committed
/*** MAKEOPTS
<category name="MENUSELECT_OPTS_app_voicemail" displayname="Voicemail Build Options" positive_output="yes" remove_on_change="apps/app_voicemail.o apps/app_directory.o">
Russell Bryant
committed
<member name="ODBC_STORAGE" displayname="Storage of Voicemail using ODBC">
<depend>unixodbc</depend>
<conflict>IMAP_STORAGE</conflict>
Russell Bryant
committed
<defaultenabled>no</defaultenabled>
</member>
Kevin P. Fleming
committed
<member name="IMAP_STORAGE" displayname="Storage of Voicemail using IMAP4">
<depend>imap_tk</depend>
<conflict>ODBC_STORAGE</conflict>
Kevin P. Fleming
committed
<defaultenabled>no</defaultenabled>
</member>
Russell Bryant
committed
</category>
***/
/* It is important to include the IMAP_STORAGE related headers
Mark Michelson
committed
* before asterisk.h since asterisk.h includes logger.h. logger.h
* and c-client.h have conflicting definitions for AST_LOG_WARNING and
* AST_LOG_DEBUG, so it's important that we use Asterisk's definitions
Mark Michelson
committed
* here instead of the c-client's
*/
#ifdef IMAP_STORAGE
#include <ctype.h>
#include <signal.h>
#include <pwd.h>
Russell Bryant
committed
#ifdef USE_SYSTEM_IMAP
#include <imap/c-client.h>
#include <imap/imap4r1.h>
#include <imap/linkage.h>
#elif defined (USE_SYSTEM_CCLIENT)
#include <c-client/c-client.h>
#include <c-client/imap4r1.h>
#include <c-client/linkage.h>
Russell Bryant
committed
#else
#include "c-client.h"
#include "imap4r1.h"
#include "linkage.h"
#endif
Russell Bryant
committed
#endif
Mark Michelson
committed
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <time.h>
#include <dirent.h>
Kevin P. Fleming
committed
#include "asterisk/lock.h"
#include "asterisk/file.h"
#include "asterisk/channel.h"
#include "asterisk/pbx.h"
#include "asterisk/config.h"
#include "asterisk/say.h"
#include "asterisk/module.h"
#include "asterisk/adsi.h"
#include "asterisk/app.h"
#include "asterisk/manager.h"
#include "asterisk/dsp.h"
#include "asterisk/localtime.h"
#include "asterisk/cli.h"
#include "asterisk/utils.h"
#include "asterisk/stringfields.h"
Matthew Fredrickson
committed
#include "asterisk/smdi.h"
#include "asterisk/taskprocessor.h"
Russell Bryant
committed
#ifdef ODBC_STORAGE
Kevin P. Fleming
committed
#include "asterisk/res_odbc.h"
#ifdef IMAP_STORAGE
static char imapserver[48];
static char imapport[8];
static char imapflags[128];
Matt O'Gorman
committed
static char imapfolder[64];
Mark Michelson
committed
static char greetingfolder[64];
static char authuser[32];
static char authpassword[42];
Matt O'Gorman
committed
static int expungeonhangup = 1;
Mark Michelson
committed
static int imapgreetings = 0;
static char delimiter = '\0';
struct vm_state;
Matt O'Gorman
committed
struct ast_vm_user;
/* Forward declarations for IMAP */
static int init_mailstream(struct vm_state *vms, int box);
Russell Bryant
committed
static void write_file(char *filename, char *buffer, unsigned long len);
static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len);
static void vm_imap_delete(int msgnum, struct vm_state *vms);
static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len);
static struct vm_state *get_vm_state_by_imapuser(char *user, int interactive);
static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, int interactive);
static void vmstate_insert(struct vm_state *vms);
static void vmstate_delete(struct vm_state *vms);
static void set_update(MAILSTREAM * stream);
static void init_vm_state(struct vm_state *vms);
static void check_msgArray(struct vm_state *vms);
static void copy_msgArray(struct vm_state *dst, struct vm_state *src);
static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, char *altfile);
static int make_gsm_file(char *dest, size_t len, char *imapuser, char *dir, int num, char *prefix);
static void get_mailbox_delimiter(MAILSTREAM *stream);
static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota);
static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int target);
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, char *introfile);
static void update_messages_by_imapuser(const char *user, unsigned long number);
Mark Michelson
committed
static int imap_remove_file (char *dir, int msgnum);
Mark Michelson
committed
static int imap_retrieve_file (char *dir, int msgnum, const char *mailbox, char *context);
Mark Michelson
committed
static int imap_delete_old_greeting (char *dir, struct vm_state *vms);
static void check_quota(struct vm_state *vms, char *mailbox);
static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
AST_LIST_ENTRY(vmstate) list;
static AST_LIST_HEAD_STATIC(vmstates, vmstate);
#endif
#define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
/* Don't modify these here; set your umask at runtime instead */
#define VOICEMAIL_DIR_MODE 0777
#define VOICEMAIL_FILE_MODE 0666
#define VOICEMAIL_CONFIG "voicemail.conf"
#define ASTERISK_USERNAME "asterisk"
Mark Michelson
committed
/* Define fast-forward, pause, restart, and reverse keys
while listening to a voicemail message - these are
strings, not characters */
#define DEFAULT_LISTEN_CONTROL_FORWARD_KEY "#"
#define DEFAULT_LISTEN_CONTROL_REVERSE_KEY "*"
#define DEFAULT_LISTEN_CONTROL_PAUSE_KEY "0"
#define DEFAULT_LISTEN_CONTROL_RESTART_KEY "2"
#define DEFAULT_LISTEN_CONTROL_STOP_KEY "13456789"
#define VALID_DTMF "1234567890*#" /* Yes ABCD are valid dtmf but what phones have those? */
/* Default mail command to mail voicemail. Change it with the
mailcmd= command in voicemail.conf */
#define SENDMAIL "/usr/sbin/sendmail -t"
#define INTRO "vm-intro"
#define MAXMSG 100
#define MAXMSGLIMIT 9999
#define MINPASSWORD 0 /*!< Default minimum mailbox password length */
#define BASELINELEN 72
#define BASEMAXINLINE 256
#define eol "\r\n"
#define MAX_DATETIME_FORMAT 512
Olle Johansson
committed
#define MAX_NUM_CID_CONTEXTS 10
#define VM_REVIEW (1 << 0) /*!< After recording, permit the caller to review the recording before saving */
#define VM_OPERATOR (1 << 1) /*!< Allow 0 to be pressed to go to 'o' extension */
#define VM_SAYCID (1 << 2) /*!< Repeat the CallerID info during envelope playback */
#define VM_SVMAIL (1 << 3) /*!< Allow the user to compose a new VM from within VoicemailMain */
#define VM_ENVELOPE (1 << 4) /*!< Play the envelope information (who-from, time received, etc.) */
#define VM_SAYDURATION (1 << 5) /*!< Play the length of the message during envelope playback */
#define VM_SKIPAFTERCMD (1 << 6) /*!< After deletion, assume caller wants to go to the next message */
#define VM_FORCENAME (1 << 7) /*!< Have new users record their name */
#define VM_FORCEGREET (1 << 8) /*!< Have new users record their greetings */
#define VM_PBXSKIP (1 << 9) /*!< Skip the [PBX] preamble in the Subject line of emails */
#define VM_DIRECFORWARD (1 << 10) /*!< Permit caller to use the Directory app for selecting to which mailbox to forward a VM */
#define VM_ATTACH (1 << 11) /*!< Attach message to voicemail notifications? */
#define VM_DELETE (1 << 12) /*!< Delete message after sending notification */
#define VM_ALLOCED (1 << 13) /*!< Structure was malloc'ed, instead of placed in a return (usually static) buffer */
#define VM_SEARCH (1 << 14) /*!< Search all contexts for a matching mailbox */
#define VM_TEMPGREETWARN (1 << 15) /*!< Remind user tempgreeting is set */
#define VM_MOVEHEARD (1 << 16) /*!< Move a "heard" message to Old after listening to it */
#define VM_MESSAGEWRAP (1 << 17) /*!< Wrap around from the last message to the first, and vice-versa */
#define ERROR_MAILBOX_FULL -200
Mark Michelson
committed
enum {
NEW_FOLDER,
OLD_FOLDER,
WORK_FOLDER,
FAMILY_FOLDER,
FRIENDS_FOLDER,
GREETINGS_FOLDER
} vm_box;
OPT_SILENT = (1 << 0),
OPT_BUSY_GREETING = (1 << 1),
OPT_UNAVAIL_GREETING = (1 << 2),
OPT_RECORDGAIN = (1 << 3),
OPT_PREPEND_MAILBOX = (1 << 4),
OPT_AUTOPLAY = (1 << 6),
Tilghman Lesher
committed
OPT_DTMFEXIT = (1 << 7),
} vm_option_flags;
enum {
OPT_ARG_RECORDGAIN = 0,
OPT_ARG_PLAYFOLDER = 1,
Tilghman Lesher
committed
OPT_ARG_DTMFEXIT = 2,
/* This *must* be the last value in this enum! */
Tilghman Lesher
committed
OPT_ARG_ARRAY_SIZE = 3,
} vm_option_args;
AST_APP_OPTIONS(vm_app_options, {
AST_APP_OPTION('s', OPT_SILENT),
AST_APP_OPTION('b', OPT_BUSY_GREETING),
AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
Tilghman Lesher
committed
AST_APP_OPTION_ARG('d', OPT_DTMFEXIT, OPT_ARG_DTMFEXIT),
AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
Kevin P. Fleming
committed
});
static int load_config(int reload);
/*! \page vmlang Voicemail Language Syntaxes Supported
\par Syntaxes supported, not really language codes.
\arg \b en - English
\arg \b de - German
\arg \b es - Spanish
\arg \b fr - French
\arg \b it - Italian
\arg \b nl - Dutch
\arg \b pt - Portuguese
\arg \b pt_BR - Portuguese (Brazil)
\arg \b gr - Greek
\arg \b no - Norwegian
\arg \b se - Swedish
\arg \b tw - Chinese (Taiwan)
German requires the following additional soundfile:
Spanish requires the following additional soundfile:
Dutch, Portuguese & Spanish require the following additional soundfiles:
\arg \b vm-INBOXs singular of 'new'
\arg \b vm-Olds singular of 'old/heard/read'
NB these are plural:
Russell Bryant
committed
Polish uses:
\arg \b vm-new-a 'new', feminine singular accusative
\arg \b vm-new-e 'new', feminine plural accusative
\arg \b vm-new-ych 'new', feminine plural genitive
\arg \b vm-old-a 'old', feminine singular accusative
\arg \b vm-old-e 'old', feminine plural accusative
\arg \b vm-old-ych 'old', feminine plural genitive
\arg \b digits/1-a 'one', not always same as 'digits/1'
\arg \b digits/2-ie 'two', not always same as 'digits/2'
Swedish uses:
\arg \b vm-nytt singular of 'new'
\arg \b vm-nya plural of 'new'
\arg \b vm-gammalt singular of 'old'
\arg \b vm-gamla plural of 'old'
\arg \b digits/ett 'one', not always same as 'digits/1'
Norwegian uses:
\arg \b vm-ny singular of 'new'
\arg \b vm-nye plural of 'new'
\arg \b vm-gammel singular of 'old'
\arg \b vm-gamle plural of 'old'
Ukrainian requires the following additional soundfile:
\arg \b vm-nove 'nove'
\arg \b vm-stare 'stare'
\arg \b digits/ua/1e 'odne'
Italian requires the following additional soundfile:
For vm_intro_it:
\arg \b vm-nuovo new
\arg \b vm-nuovi new plural
\arg \b vm-vecchio old
\arg \b vm-vecchi old plural
Chinese (Taiwan) requires the following additional soundfile:
\arg \b vm-tong A class-word for call (tong1)
\arg \b vm-ri A class-word for day (ri4)
\arg \b vm-you You (ni3)
\arg \b vm-haveno Have no (mei2 you3)
\arg \b vm-listen To listen (yao4 ting1)
\note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
spelled among others when you have to change folder. For the above reasons, vm-INBOX
and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
struct baseio {
int iocp;
int iolen;
int linelength;
int ateof;
unsigned char iobuf[BASEMAXINLINE];
};
/*! Structure for linked list of users
* Use ast_vm_user_destroy() to free one of these structures. */
struct ast_vm_user {
char context[AST_MAX_CONTEXT]; /*!< Voicemail context */
char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
char password[80]; /*!< Secret pin code, numbers only */
char fullname[80]; /*!< Full name, for directory app */
char email[80]; /*!< E-mail address */
char pager[80]; /*!< E-mail address to pager (no attachment) */
char serveremail[80]; /*!< From: Mail address */
char mailcmd[160]; /*!< Configurable mail command */
char language[MAX_LANGUAGE]; /*!< Config: Language setting */
char zonetag[80]; /*!< Time zone */
char callback[80];
char dialout[80];
char uniqueid[80]; /*!< Unique integer identifier */
unsigned int flags; /*!< VM_ flags */
int saydurationm;
int maxmsg; /*!< Maximum number of msgs per folder for this mailbox */
int maxdeletedmsg; /*!< Maximum number of deleted msgs saved for this mailbox */
int maxsecs; /*!< Maximum number of seconds per message for this mailbox */
char imapuser[80]; /*!< IMAP server login */
char imappassword[80]; /*!< IMAP server password if authpassword not defined */
double volgain; /*!< Volume gain for voicemails sent via email */
AST_LIST_ENTRY(ast_vm_user) list;
};
struct vm_zone {
Russell Bryant
committed
AST_LIST_ENTRY(vm_zone) list;
char name[80];
char timezone[80];
char msg_format[512];
};
#define VMSTATE_MAX_MSG_ARRAY 256
Martin Pycko
committed
struct vm_state {
Olle Johansson
committed
char curbox[80];
char username[80];
char curdir[PATH_MAX];
char vmbox[PATH_MAX];
char fn[PATH_MAX];
char fn2[PATH_MAX];
char intro[PATH_MAX];
int *deleted;
int *heard;
Martin Pycko
committed
int curmsg;
int lastmsg;
int newmessages;
int oldmessages;
int starting;
int repeats;
int updated; /*!< decremented on each mail check until 1 -allows delay */
long msgArray[VMSTATE_MAX_MSG_ARRAY];
MAILSTREAM *mailstream;
int vmArrayIndex;
char imapuser[80]; /*!< IMAP server login */
int interactive;
unsigned int quota_limit;
unsigned int quota_usage;
struct vm_state *persist_vms;
#endif
Martin Pycko
committed
};
Russell Bryant
committed
#ifdef ODBC_STORAGE
static char odbc_database[80];
static char odbc_table[80];
Mark Michelson
committed
#define RETRIEVE(a,b,c,d) retrieve_file(a,b)
#define DISPOSE(a,b) remove_file(a,b)
#define STORE(a,b,c,d,e,f,g,h,i,j) store_file(a,b,c,d)
#define EXISTS(a,b,c,d) (message_exists(a,b))
#define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
#define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
#define DELETE(a,b,c) (delete_file(a,b))
#else
Mark Michelson
committed
#define RETRIEVE(a,b,c,d) (imap_retrieve_file(a,b,c,d ))
#define DISPOSE(a,b) (imap_remove_file(a,b))
#define STORE(a,b,c,d,e,f,g,h,i,j) (imap_store_file(a,b,c,d,e,f,g,h,i,j))
#define EXISTS(a,b,c,d) (ast_fileexists(c, NULL, d) > 0)
#define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
#define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
#define IMAP_DELETE(a,b,c,d) (vm_imap_delete(b,d))
#define DELETE(a,b,c) (vm_delete(c))
#else
Mark Michelson
committed
#define RETRIEVE(a,b,c,d)
#define STORE(a,b,c,d,e,f,g,h,i,j)
#define EXISTS(a,b,c,d) (ast_fileexists(c, NULL, d) > 0)
#define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
#define COPY(a,b,c,d,e,f,g,h) (copy_plain_file(g,h));
#define DELETE(a,b,c) (vm_delete(c))
#endif
Kevin P. Fleming
committed
static char VM_SPOOL_DIR[PATH_MAX];
Olle Johansson
committed
static char ext_pass_cmd[128];
static char ext_pass_check_cmd[128];
Jason Parker
committed
#define PWDCHANGE_INTERNAL (1 << 1)
#define PWDCHANGE_EXTERNAL (1 << 2)
static int pwdchange = PWDCHANGE_INTERNAL;
Kevin P. Fleming
committed
#ifdef ODBC_STORAGE
#define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
Russell Bryant
committed
# ifdef IMAP_STORAGE
# define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
# else
# define tdesc "Comedian Mail (Voicemail System)"
# endif
static char userscontext[AST_MAX_EXTENSION] = "default";
static char *synopsis_vm = "Leave a Voicemail message";
Tilghman Lesher
committed
" VoiceMail(mailbox[@context][&mailbox[@context]][...][,options]): This\n"
"application allows the calling party to leave a message for the specified\n"
"list of mailboxes. When multiple mailboxes are specified, the greeting will\n"
"be taken from the first mailbox specified. Dialplan execution will stop if the\n"
"specified mailbox does not exist.\n"
" The Voicemail application will exit if any of the following DTMF digits are\n"
"received:\n"
" 0 - Jump to the 'o' extension in the current dialplan context.\n"
" * - Jump to the 'a' extension in the current dialplan context.\n"
" This application will set the following channel variable upon completion:\n"
" VMSTATUS - This indicates the status of the execution of the VoiceMail\n"
" application. The possible values are:\n"
" SUCCESS | USEREXIT | FAILED\n\n"
" Options:\n"
Tilghman Lesher
committed
" b - Play the 'busy' greeting to the calling party.\n"
" d([c]) - Accept digits for a new extension in context c, if played during\n"
" the greeting. Context defaults to the current context.\n"
" g(#) - Use the specified amount of gain when recording the voicemail\n"
" message. The units are whole-number decibels (dB).\n"
" s - Skip the playback of instructions for leaving a message to the\n"
" calling party.\n"
" u - Play the 'unavailable' greeting.\n";
static char *synopsis_vmain = "Check Voicemail messages";
Tilghman Lesher
committed
" VoiceMailMain([mailbox][@context][,options]): This application allows the\n"
"calling party to check voicemail messages. A specific mailbox, and optional\n"
"corresponding context, may be specified. If a mailbox is not provided, the\n"
"calling party will be prompted to enter one. If a context is not specified,\n"
"the 'default' context will be used.\n\n"
" Options:\n"
" p - Consider the mailbox parameter as a prefix to the mailbox that\n"
" is entered by the caller.\n"
" g(#) - Use the specified amount of gain when recording a voicemail\n"
" message. The units are whole-number decibels (dB).\n"
" s - Skip checking the passcode for the mailbox.\n"
" a(#) - Skip folder prompt and go directly to folder specified.\n"
" Defaults to INBOX\n";
static char *synopsis_vm_box_exists =
"Check to see if Voicemail mailbox exists";
static char *descrip_vm_box_exists =
Tilghman Lesher
committed
" MailboxExists(mailbox[@context][,options]): Check to see if the specified\n"
"mailbox exists. If no voicemail context is specified, the 'default' context\n"
"will be used.\n"
" This application will set the following channel variable upon completion:\n"
" VMBOXEXISTSSTATUS - This will contain the status of the execution of the\n"
" MailboxExists application. Possible values include:\n"
" SUCCESS | FAILED\n\n"
Russell Bryant
committed
" Options: (none)\n";
static char *synopsis_vmauthenticate = "Authenticate with Voicemail passwords";
static char *descrip_vmauthenticate =
Tilghman Lesher
committed
" VMAuthenticate([mailbox][@context][,options]): This application behaves the\n"
"same way as the Authenticate application, but the passwords are taken from\n"
"voicemail.conf.\n"
" If the mailbox is specified, only that mailbox's password will be considered\n"
"valid. If the mailbox is not specified, the channel variable AUTH_MAILBOX will\n"
"be set with the authenticated mailbox.\n\n"
" Options:\n"
" s - Skip playing the initial prompts.\n";
/* Leave a message */
static char *app = "VoiceMail";
/* Check mail, control, etc */
static char *app2 = "VoiceMailMain";
static char *app3 = "MailboxExists";
static char *app4 = "VMAuthenticate";
static AST_LIST_HEAD_STATIC(users, ast_vm_user);
Russell Bryant
committed
static AST_LIST_HEAD_STATIC(zones, vm_zone);
static int maxsilence;
static int maxmsg;
static int silencethreshold = 128;
static char serveremail[80];
Olle Johansson
committed
static char mailcmd[160]; /* Configurable mail cmd */
static char externnotify[160];
Matthew Fredrickson
committed
static struct ast_smdi_interface *smdi_iface = NULL;
static char vmfmts[80];
Tilghman Lesher
committed
static double volgain;
static int vmminsecs;
static int vmmaxsecs;
static int maxgreet;
static int skipms;
static int maxlogins;
static int minpassword;
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
/*! Poll mailboxes for changes since there is something external to
* app_voicemail that may change them. */
static unsigned int poll_mailboxes;
/*! Polling frequency */
static unsigned int poll_freq;
/*! By default, poll every 30 seconds */
#define DEFAULT_POLL_FREQ 30
AST_MUTEX_DEFINE_STATIC(poll_lock);
static ast_cond_t poll_cond = PTHREAD_COND_INITIALIZER;
static pthread_t poll_thread = AST_PTHREADT_NULL;
static unsigned char poll_thread_run;
/*! Subscription to ... MWI event subscriptions */
static struct ast_event_sub *mwi_sub_sub;
/*! Subscription to ... MWI event un-subscriptions */
static struct ast_event_sub *mwi_unsub_sub;
/*!
* \brief An MWI subscription
*
* This is so we can keep track of which mailboxes are subscribed to.
* This way, we know which mailboxes to poll when the pollmailboxes
* option is being used.
*/
struct mwi_sub {
AST_RWLIST_ENTRY(mwi_sub) entry;
int old_new;
int old_old;
uint32_t uniqueid;
char mailbox[1];
};
struct mwi_sub_task {
const char *mailbox;
const char *context;
uint32_t uniqueid;
};
static struct ast_taskprocessor *mwi_subscription_tps;
static AST_RWLIST_HEAD_STATIC(mwi_subs, mwi_sub);
Mark Michelson
committed
/* custom audio control prompts for voicemail playback */
static char listen_control_forward_key[12];
static char listen_control_reverse_key[12];
static char listen_control_pause_key[12];
static char listen_control_restart_key[12];
static char listen_control_stop_key[12];
static char vm_password[80] = "vm-password";
static char vm_newpassword[80] = "vm-newpassword";
static char vm_passchanged[80] = "vm-passchanged";
static char vm_reenterpassword[80] = "vm-reenterpassword";
static char vm_mismatch[80] = "vm-mismatch";
static char vm_invalid_password[80] = "vm-invalid-password";
static struct ast_flags globalflags = {0};
static int saydurationminfo;
Joshua Colp
committed
static char dialcontext[AST_MAX_CONTEXT] = "";
static char callcontext[AST_MAX_CONTEXT] = "";
static char exitcontext[AST_MAX_CONTEXT] = "";
static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
static char *emailbody = NULL;
static char *emailsubject = NULL;
static char *pagerbody = NULL;
static char *pagersubject = NULL;
static char fromstring[100];
static char pagerfromstring[100];
static char charset[32] = "ISO-8859-1";
static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
Kevin P. Fleming
committed
static char emaildateformat[32] = "%A, %B %d, %Y at %r";
static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
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);
static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
signed char record_gain, struct vm_state *vms);
static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msgnum, long duration, char *fmt, char *cidnum, char *cidname);
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 *attach2, char *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category, int imap);
static void apply_options(struct ast_vm_user *vmu, const char *options);
static int add_email_attachment(FILE *p, struct ast_vm_user *vmu, char *format, char *attach, char *greeting_attachment, char *mailbox, char *bound, char *filename, int last, int msgnum);
Mark Michelson
committed
static int is_valid_dtmf(const char *key);
#if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
#endif
/*!
* \brief Sets default voicemail system options to a voicemail user.
*
* This applies select global settings to a newly created (dynamic) instance of a voicemail user.
* - all the globalflags
* - the saydurationminfo
* - the callcontext
* - the dialcontext
* - the exitcontext
* - vmmaxsecs, vmmaxmsg, maxdeletedmsg
* - volume gain.
*/
static void populate_defaults(struct ast_vm_user *vmu)
ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
Kevin P. Fleming
committed
if (saydurationminfo)
vmu->saydurationm = saydurationminfo;
Joshua Colp
committed
ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
if (vmmaxsecs)
vmu->maxsecs = vmmaxsecs;
Kevin P. Fleming
committed
if (maxmsg)
vmu->maxmsg = maxmsg;
if (maxdeletedmsg)
vmu->maxdeletedmsg = maxdeletedmsg;
Tilghman Lesher
committed
vmu->volgain = volgain;
/*!
* \brief Sets a a specific property value.
* \param vmu The voicemail user object to work with.
* \param var The name of the property to be set.
* \param value The value to be set to the property.
*
* The property name must be one of the understood properties. See the source for details.
*/
static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
} else if (!strcasecmp(var, "attachfmt")) {
ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
ast_copy_string(vmu->language, value, sizeof(vmu->language));
ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
#ifdef IMAP_STORAGE
} else if (!strcasecmp(var, "imapuser")) {
ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
Matt O'Gorman
committed
} else if (!strcasecmp(var, "imappassword")) {
ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
} else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
ast_set2_flag(vmu, ast_true(value), VM_DELETE);
ast_set2_flag(vmu, ast_true(value), VM_SAYCID);
} else if (!strcasecmp(var, "sendvoicemail")) {
ast_set2_flag(vmu, ast_true(value), VM_SVMAIL);
ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
} else if (!strcasecmp(var, "tempgreetwarn")) {
ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);
} else if (!strcasecmp(var, "messagewrap")){
ast_set2_flag(vmu, ast_true(value), VM_MESSAGEWRAP);
ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);
ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);
} else if (!strcasecmp(var, "sayduration")) {
ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);
} else if (!strcasecmp(var, "saydurationm")) {
if (sscanf(value, "%d", &x) == 1) {
vmu->saydurationm = x;
} else {
ast_log(AST_LOG_WARNING, "Invalid min duration for say duration\n");
ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);
} else if (!strcasecmp(var, "forcegreetings")) {
ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);
ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
} else if (!strcasecmp(var, "maxmessage") || !strcasecmp(var, "maxsecs")) {
if (vmu->maxsecs <= 0) {
ast_log(AST_LOG_WARNING, "Invalid max message length of %s. Using global value %d\n", value, vmmaxsecs);
vmu->maxsecs = vmmaxsecs;
} else {
vmu->maxsecs = atoi(value);
}
if (!strcasecmp(var, "maxmessage"))
ast_log(AST_LOG_WARNING, "Option 'maxmessage' has been deprecated in favor of 'maxsecs'. Please make that change in your voicemail config.\n");
} else if (!strcasecmp(var, "maxmsg")) {
vmu->maxmsg = atoi(value);
ast_log(AST_LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %d\n", value, MAXMSG);
vmu->maxmsg = MAXMSG;
} else if (vmu->maxmsg > MAXMSGLIMIT) {
ast_log(AST_LOG_WARNING, "Maximum number of messages per folder is %d. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
vmu->maxmsg = MAXMSGLIMIT;
}
} else if (!strcasecmp(var, "backupdeleted")) {
if (sscanf(value, "%d", &x) == 1)
vmu->maxdeletedmsg = x;
else if (ast_true(value))
vmu->maxdeletedmsg = MAXMSG;
else
vmu->maxdeletedmsg = 0;
if (vmu->maxdeletedmsg < 0) {
ast_log(AST_LOG_WARNING, "Invalid number of deleted messages saved per mailbox backupdeleted=%s. Using default value %d\n", value, MAXMSG);
vmu->maxdeletedmsg = MAXMSG;
} else if (vmu->maxdeletedmsg > MAXMSGLIMIT) {
ast_log(AST_LOG_WARNING, "Maximum number of deleted messages saved per mailbox is %d. Cannot accept value backupdeleted=%s\n", MAXMSGLIMIT, value);
vmu->maxdeletedmsg = MAXMSGLIMIT;
}
Tilghman Lesher
committed
} else if (!strcasecmp(var, "volgain")) {
sscanf(value, "%lf", &vmu->volgain);
} else if (!strcasecmp(var, "options")) {
apply_options(vmu, value);
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
static char *vm_check_password_shell(char *command, char *buf, size_t len)
{
int fds[2], pid = 0;
memset(buf, 0, len);
if (pipe(fds)) {
snprintf(buf, len, "FAILURE: Pipe failed: %s", strerror(errno));
} else {
/* good to go*/
pid = ast_safe_fork(0);
if (pid < 0) {
/* ok maybe not */
close(fds[0]);
close(fds[1]);
snprintf(buf, len, "FAILURE: Fork failed");
} else if (pid) {
/* parent */
close(fds[1]);
read(fds[0], buf, len);
close(fds[0]);
} else {
/* child */
AST_DECLARE_APP_ARGS(arg,
AST_APP_ARG(v)[20];
);
char *mycmd = ast_strdupa(command);
close(fds[0]);
dup2(fds[1], STDOUT_FILENO);
close(fds[1]);
ast_close_fds_above_n(STDOUT_FILENO);
AST_NONSTANDARD_APP_ARGS(arg, mycmd, ' ');
execv(arg.v[0], arg.v);
printf("FAILURE: %s", strerror(errno));
_exit(0);
}
}
return buf;
}
/*!
* \brief Check that password meets minimum required length
* \param vmu The voicemail user to change the password for.
* \param password The password string to check
*
* \return zero on ok, 1 on not ok.
*/
static int check_password(struct ast_vm_user *vmu, char *password)
{
/* check minimum length */
if (strlen(password) < minpassword)
return 1;
if (!ast_strlen_zero(ext_pass_check_cmd)) {
char cmd[255], buf[255];
ast_log(LOG_DEBUG, "Verify password policies for %s\n", password);
snprintf(cmd, sizeof(cmd), "%s %s %s %s %s", ext_pass_check_cmd, vmu->mailbox, vmu->context, vmu->password, password);
if (vm_check_password_shell(cmd, buf, sizeof(buf))) {
ast_debug(5, "Result: %s\n", buf);
if (!strncasecmp(buf, "VALID", 5)) {
ast_debug(3, "Passed password check: '%s'\n", buf);
return 0;
} else if (!strncasecmp(buf, "FAILURE", 7)) {
ast_log(LOG_WARNING, "Unable to execute password validation script: '%s'.\n", buf);
return 0;
} else {
ast_log(LOG_NOTICE, "Password doesn't match policies for user %s %s\n", vmu->mailbox, password);
return 1;
}
}
}
return 0;
}
/*!
* \brief Performs a change of the voicemail passowrd in the realtime engine.
* \param vmu The voicemail user to change the password for.
* \param password The new value to be set to the password for this user.
*
* This only works if the voicemail user has a unique id, and if there is a realtime engine configured.
* This is called from the (top level) vm_change_password.
*
* \return zero on success, -1 on error.
*/
static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
int res;
if (!ast_strlen_zero(vmu->uniqueid)) {
res = ast_update_realtime("voicemail", "uniqueid", vmu->uniqueid, "password", password, NULL);
Russell Bryant
committed
if (res > 0) {
ast_copy_string(vmu->password, password, sizeof(vmu->password));
Russell Bryant
committed
res = 0;
} else if (!res) {
res = -1;
}
}
/*!
* \brief Destructively Parse options and apply.
*/
static void apply_options(struct ast_vm_user *vmu, const char *options)
char *stringp;
char *s;
char *var, *value;
stringp = ast_strdupa(options);
while ((s = strsep(&stringp, "|"))) {
value = s;
if ((var = strsep(&value, "=")) && value) {
apply_option(vmu, var, value);
}
}
/*!
* \brief Loads the options specific to a voicemail user.
*
* This is called when a vm_user structure is being set up, such as from load_options.
*/
static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
{
struct ast_variable *tmp;
tmp = var;
while (tmp) {
if (!strcasecmp(tmp->name, "vmsecret")) {
ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
} else if (!strcasecmp(tmp->name, "secret") || !strcasecmp(tmp->name, "password")) { /* don't overwrite vmsecret if it exists */
if (ast_strlen_zero(retval->password))
ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
} else if (!strcasecmp(tmp->name, "uniqueid")) {
ast_copy_string(retval->uniqueid, tmp->value, sizeof(retval->uniqueid));
} else if (!strcasecmp(tmp->name, "pager")) {
ast_copy_string(retval->pager, tmp->value, sizeof(retval->pager));
} else if (!strcasecmp(tmp->name, "email")) {
ast_copy_string(retval->email, tmp->value, sizeof(retval->email));
} else if (!strcasecmp(tmp->name, "fullname")) {
ast_copy_string(retval->fullname, tmp->value, sizeof(retval->fullname));
} else if (!strcasecmp(tmp->name, "context")) {
ast_copy_string(retval->context, tmp->value, sizeof(retval->context));
#ifdef IMAP_STORAGE
} else if (!strcasecmp(tmp->name, "imapuser")) {
ast_copy_string(retval->imapuser, tmp->value, sizeof(retval->imapuser));
} else if (!strcasecmp(tmp->name, "imappassword")) {
ast_copy_string(retval->imappassword, tmp->value, sizeof(retval->imappassword));
#endif
} else
apply_option(retval, tmp->name, tmp->value);
tmp = tmp->next;
}
}
/*!
* \brief Determines if a DTMF key entered is valid.
* \param key The character to be compared. expects a single character. Though is capable of handling a string, this is internally copies using ast_strdupa.
*
* Tests the character entered against the set of valid DTMF characters.
* \return 1 if the character entered is a valid DTMF digit, 0 if the character is invalid.
*/
Mark Michelson
committed
static int is_valid_dtmf(const char *key)
{
int i;
char *local_key = ast_strdupa(key);