Asterisk contains the ability to store voicemail inside the database using the ODBC connector. This is useful in a clustered environment where you want to abstract the voicemail data from the local system so that multiple Asterisk boxes have access to the same data. Of course, you have to take into consideration that you are centralizing a part of Asterisk, and you need to take actions to protect that data, such as regular backups, and possibly clustering the database backend using replication. If you are using PostgreSQL, there are some good projects for doing this: PGcluster (http://pgfoundry.org/projects/pgcluster/) and Slony-I (http://gborg.postgresql.org/project/slony1/projdisplay.php).
Asterisk stores the voicemail inside a Binary Large Object (BLOB). When retrieving the data, it pulls the information out of the BLOB and temporarily stores it on the hard drive while it is being played back to the user. Asterisk then removes the BLOB and records from the database when the user deletes the voicemail. Many databases, such as MySQL, contain native support for BLOBs, but PostgreSQL has a couple of extra steps required to utilize this functionality that we’ll explore in this section. When you’re done, you’ll be able to record, play back, and delete voicemail data from the database just as if it were stored on the local hard drive.
This section builds upon previous configuration sections in this
chapter. If you have not already done so, be sure to follow the steps
in the the section called “Installing the Database”” and the section called “Installing and Configuring ODBC”” sections before continuing.
In the the section called “Installing and Configuring ODBC”” section, be
sure you have enabled ODBC_STORAGE
in the
menuselect system under Voicemail Options.
We have to tell PostgreSQL how to handle the large objects. This includes creating a trigger to clean up the data when we delete a record from the database that references a large object.
Connect to the database as the asterisk user from the console:
# psql -h localhost -U asterisk asterisk
Password:
At the PostgreSQL console, run the following script to create the large object type:
CREATE FUNCTION loin (cstring) RETURNS lo AS 'oidin' LANGUAGE internal IMMUTABLE STRICT; CREATE FUNCTION loout (lo) RETURNS cstring AS 'oidout' LANGUAGE internal IMMUTABLE STRICT; CREATE FUNCTION lorecv (internal) RETURNS lo AS 'oidrecv' LANGUAGE internal IMMUTABLE STRICT; CREATE FUNCTION losend (lo) RETURNS bytea AS 'oidrecv' LANGUAGE internal IMMUTABLE STRICT; CREATE TYPE lo ( INPUT = loin, OUTPUT = loout, RECEIVE = lorecv, SEND = losend, INTERNALLENGTH = 4, PASSEDBYVALUE ); CREATE CAST (lo AS oid) WITHOUT FUNCTION AS IMPLICIT; CREATE CAST (oid AS lo) WITHOUT FUNCTION AS IMPLICIT;
We’ll be making use of the PostgreSQL procedural language called pgSQL/PL to create a function. This function will be called from a trigger that gets executed whenever we modify or delete a record from the table used to store voicemail. This is so the data is cleaned up and not left as an orphan in the database:
CREATE FUNCTION vm_lo_cleanup() RETURNS "trigger" AS $$ declare msgcount INTEGER; begin -- raise notice 'Starting lo_cleanup function for large object with oid %',old.recording; -- If it is an update action but the BLOB (lo) field was not changed, dont do anything if (TG_OP = 'UPDATE') then if ((old.recording = new.recording) or (old.recording is NULL)) then raise notice 'Not cleaning up the large object table, as recording has not changed'; return new; end if; end if; if (old.recording IS NOT NULL) then SELECT INTO msgcount COUNT(*) AS COUNT FROM voicemessages WHERE recording = old.recording; if (msgcount > 0) then raise notice 'Not deleting record from the large object table, as object is still referenced'; return new; else perform lo_unlink(old.recording); if found then raise notice 'Cleaning up the large object table'; return new; else raise exception 'Failed to cleanup the large object table'; return old; end if; end if; else raise notice 'No need to cleanup the large object table, no recording on old row'; return new; end if; end$$ LANGUAGE plpgsql;
We’re going to create a table called
voicemessages
where the voicemail information will be
stored:
CREATE TABLE voicemessages ( uniqueid serial PRIMARY KEY, msgnum int4, dir varchar(80), context varchar(80), macrocontext varchar(80), callerid varchar(40), origtime varchar(40), duration varchar(20), mailboxuser varchar(80), mailboxcontext varchar(80), recording lo, label varchar(30), "read" bool DEFAULT false );
And now we need to associate a trigger with our newly
created table in order to perform cleanup whenever we make a change or
deletion from the voicemessages
table:
CREATE TRIGGER vm_cleanup AFTER DELETE OR UPDATE ON voicemessages FOR EACH ROW EXECUTE PROCEDURE vm_lo_cleanup();
There isn’t much to add to the voicemail.conf file to enable the
ODBC voicemail storage. In fact, it’s only three lines! Generally, you
probably have multiple format types defined in the
[general]
section of
voicemail.conf, however we need to set this to a
single format. The wav49 format is a compressed WAV file format that
should be playable on both Linux and Microsoft Windows desktops.
The odbcstorage
option points at the name you
defined in the res_odbc.conf file (if you’ve been
following along in this chapter, then we called it
asterisk). The odbctable
option
refers to the table where voicemail information should be stored. In the
examples in this chapter we use the table named
voicemessages
:
[general] format=wav49 odbcstorage=asterisk odbctable=voicemessages
You may want to create a separate voicemail context, or you can utilize the default voicemail context:
[default] 1000 => 1000,J.P. Wiser
Now connect to your Asterisk console and unload then reload the app_voicemail.so module:
*CLI> module unload app_voicemail.so == Unregistered application 'VoiceMail' == Unregistered application 'VoiceMailMain' == Unregistered application 'MailboxExists' == Unregistered application 'VMAuthenticate' *CLI> module load app_voicemail.so Loaded /usr/lib/asterisk/modules/app_voicemail.so => (Comedian Mail (Voicemail System)) == Registered application 'VoiceMail' == Registered application 'VoiceMailMain' == Registered application 'MailboxExists' == Registered application 'VMAuthenticate' == Parsing '/etc/asterisk/voicemail.conf': Found
And verify that your new mailbox loaded successfully:
*CLI> voicemail show users for default
Context Mbox User Zone NewMsg
default 1000 J.P. Wiser 0
Let’s create some simple dialplan logic to leave and retrieve some voicemail from our test voicemail box. We can use the simple dialplan logic as follows:
[odbc_vm_test] exten => 100,1,Voicemail(1000@default) ; leave a voicemail exten => 200,1,VoicemailMain(1000@default) ; retrieve a voicemail
Once you’ve updated your extensions.conf file, be sure to reload the dialplan:
*CLI> dialplan reload
You
can either include
the odbc_vm_test
context into a context accessible
by an existing user, or create a separate user to test with. If you wish
to do the latter, you could define a new SIP user in sip.conf
like so (this will work assuming the
phone is on the local LAN):
[odbc_test_user] type=friend secret=supersecret context=odbc_vm_test host=dynamic qualify=yes disallow=all allow=ulaw allow=gsm
Don’t forget to reload the SIP module:
*CLI> module reload chan_sip.so
And verify that the SIP user exists:
*CLI> sip show users like odbc_test_user
Username Secret Accountcode Def.Context ACL NAT
odbc_test_user supersecret odbc_vm_test No RFC3581
Then configure your phone or client with the username odbc_test_user and password supersecret, and then place a call to extension 100 to leave a voicemail. If successful, you should see something like:
-- Executing VoiceMail("SIP/odbc_test_user-10228cac", "1000@default") in new stack -- Playing 'vm-intro' (language 'en') -- Playing 'beep' (language 'en') -- Recording the message -- x=0, open writing: /var/spool/asterisk/voicemail/default/1000/tmp/dlZunm format: wav49, 0x101f6534 -- User ended message by pressing # -- Playing 'auth-thankyou' (language 'en') == Parsing '/var/spool/asterisk/voicemail/default/1000/INBOX/msg0000.txt': Found
We
can now make use of the psql
application again to make sure the recording really did make it into the
database:
# psql -h localhost -U asterisk asterisk
Password:
Then run a SELECT
statement to
verify that you have some data in the voicemessages
table:
localhost=# SELECT id,dir,callerid,mailboxcontext,recording FROM voicemessages; id | dir | callerid | mailboxcontext | recording ---+------------------------------------------+--------------+---------------+------- 1 | /var/spool/asterisk/voicemail/default/1000/INBOX | +18005551212 | default | 47395 (1 row)
If the recording was placed in the database, we should
get a row back. You’ll notice that the recording column contains a
number (which will most certainly be different from that listed here),
which is really the object ID of the large object stored in a system
table. Let’s verify that the large object exists in this system table
with the lo_list
command:
localhost=# \lo_list
Large objects
ID | Description
-------+-------------
47395 |
(1 row)
What we’re verifying is that the object ID in the
voicemessages
table matches that listed in the large
object system table. We can also pull the data out of the database and
store it to the hard drive so we can play the file back to make sure our
message was saved correctly:
localhost=# \lo_export 47395 /tmp/voicemail-47395.wav
lo_export
Then verify the audio with your favorite audio application, such
as the play
application:
# play /tmp/voicemail-47395.wav
Input Filename : /tmp/voicemail-47395.wav
Sample Size : 8-bits
Sample Encoding: wav
Channels : 1
Sample Rate : 8000
Time: 00:06.22 [00:00.00] of 00:00.00 ( 0.0%) Output Buffer: 298.36K
Done.
And now that we’ve confirmed everything was stored in
the database correctly, we can try listening to it via the
VoicemailMain()
application by dialing extension
200:
*CLI> -- Executing VoiceMailMain("SIP/odbc_test_user-10228cac", "1000@default") in new stack -- Playing 'vm-password' (language 'en') -- Playing 'vm-youhave' (language 'en') -- Playing 'digits/1' (language 'en') -- Playing 'vm-INBOX' (language 'en') -- Playing 'vm-message' (language 'en') -- Playing 'vm-onefor' (language 'en') -- Playing 'vm-INBOX' (language 'en') -- Playing 'vm-messages' (language 'en') -- Playing 'vm-opts' (language 'en') -- Playing 'vm-first' (language 'en') -- Playing 'vm-message' (language 'en') == Parsing '/var/spool/asterisk/voicemail/default/1000/INBOX/msg0000.txt': Found