mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-11-03 20:38:59 +00:00 
			
		
		
		
	https://origsvn.digium.com/svn/asterisk/branches/1.4 ........ r107826 | qwell | 2008-03-11 18:37:05 -0500 (Tue, 11 Mar 2008) | 7 lines Update documentation for pgsql ODBC voicemail. (closes issue #12186) Reported by: jsmith Patches: vm_pgsql_doc_update.patch uploaded by jsmith (license 15) ........ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@107827 65c4cc65-6c06-0410-ace0-fbb531ad65f3
		
			
				
	
	
		
			454 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			454 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
GETTING ODBC STORAGE WITH POSTGRESQL WORKING WITH VOICEMAIL
 | 
						|
 | 
						|
1) Install PostgreSQL, PostgreSQL-devel, unixODBC, and unixODBC-devel, and
 | 
						|
PostgreSQL-ODBC.  Make sure PostgreSQL is running and listening on a TCP socket.
 | 
						|
 | 
						|
2) Log into your server as root, and then type:
 | 
						|
 | 
						|
[root@localhost ~]# su - postgres
 | 
						|
 | 
						|
This will log you into the system as the "postgres" user, so that you can
 | 
						|
create a new role and database within the PostgreSQL database system.  At the
 | 
						|
new prompt, type:
 | 
						|
 | 
						|
$ createuser -s -D -R -l -P -e asterisk
 | 
						|
Enter password for new role: 
 | 
						|
Enter it again: 
 | 
						|
 | 
						|
Obviously you should enter a password when prompted.  This creates the
 | 
						|
database role (or user).
 | 
						|
 | 
						|
Next we need to create the asterisk database.  Type:
 | 
						|
 | 
						|
$ createdb -O asterisk -e asterisk
 | 
						|
 | 
						|
This creates the database and sets the owner of the database to the asterisk
 | 
						|
role.
 | 
						|
 | 
						|
 | 
						|
Next, make sure that
 | 
						|
you are using md5 authentication for the database user.  The line in my
 | 
						|
/var/lib/pgsql/data/pg_hba.conf looks like:
 | 
						|
 | 
						|
# "local" is for Unix domain socket connections only
 | 
						|
local   asterisk    asterisk                           md5
 | 
						|
local   all         all                               ident sameuser
 | 
						|
# IPv4 local connections:
 | 
						|
host    all         all         127.0.0.1/32          md5
 | 
						|
 | 
						|
As soon as you're done editing that file, log out as the postgres user.
 | 
						|
 | 
						|
3) Make sure you have the PostgreSQL odbc driver setup in /etc/odbcinst.ini.
 | 
						|
Mine looks like:
 | 
						|
 | 
						|
[PostgreSQL]
 | 
						|
Description     = ODBC for PostgreSQL
 | 
						|
Driver          = /usr/lib/libodbcpsql.so
 | 
						|
Setup           = /usr/lib/libodbcpsqlS.so
 | 
						|
FileUsage       = 1
 | 
						|
 | 
						|
You can confirm that unixODBC is seeing the driver by typing:
 | 
						|
 | 
						|
[jsmith2@localhost tmp]$ odbcinst -q -d
 | 
						|
[PostgreSQL]
 | 
						|
 | 
						|
 | 
						|
4) Setup a DSN in /etc/odbc.ini, pointing at the PostgreSQL database and
 | 
						|
driver.  Mine looks like:
 | 
						|
 | 
						|
[testing]
 | 
						|
Description           = ODBC Testing
 | 
						|
Driver                = PostgreSQL
 | 
						|
Trace                 = No
 | 
						|
TraceFile             = sql.log
 | 
						|
Database              = asterisk
 | 
						|
Servername            = 127.0.0.1
 | 
						|
UserName              = asterisk
 | 
						|
Password              = supersecret
 | 
						|
Port                  = 5432
 | 
						|
ReadOnly              = No
 | 
						|
RowVersioning         = No
 | 
						|
ShowSystemTables      = No
 | 
						|
ShowOidColumn         = No
 | 
						|
FakeOidIndex          = No
 | 
						|
ConnSettings          =
 | 
						|
 | 
						|
You can confirm that unixODBC sees your DSN by typing:
 | 
						|
 | 
						|
[jsmith2@localhost tmp]$ odbcinst -q -s
 | 
						|
[testing]
 | 
						|
 | 
						|
 | 
						|
5) Test your database connectivity through ODBC.  If this doesn't work,
 | 
						|
something is wrong with your ODBC setup.
 | 
						|
 | 
						|
[jsmith2@localhost tmp]$ echo "select 1" | isql -v testing
 | 
						|
+---------------------------------------+
 | 
						|
| Connected!                            |
 | 
						|
|                                       |
 | 
						|
| sql-statement                         |
 | 
						|
| help [tablename]                      |
 | 
						|
| quit                                  |
 | 
						|
|                                       |
 | 
						|
+---------------------------------------+
 | 
						|
SQL> +------------+
 | 
						|
| ?column?   |
 | 
						|
+------------+
 | 
						|
| 1          |
 | 
						|
+------------+
 | 
						|
SQLRowCount returns 1
 | 
						|
1 rows fetched
 | 
						|
 | 
						|
If your ODBC connectivity to PostgreSQL isn't working, you'll see an error
 | 
						|
message instead, like this:
 | 
						|
 | 
						|
[jsmith2@localhost tmp]$ echo "select 1" | isql -v testing
 | 
						|
[S1000][unixODBC]Could not connect to the server;
 | 
						|
Could not connect to remote socket.
 | 
						|
[ISQL]ERROR: Could not SQLConnect
 | 
						|
bash: echo: write error: Broken pipe
 | 
						|
 | 
						|
6) Compile Asterisk with support for ODBC voicemail.  Go to your Asterisk
 | 
						|
source directory and run `make menuselect`.  Under "Voicemail Build Options",
 | 
						|
enable "ODBC_STORAGE".
 | 
						|
# See doc/README.odbcstorage for more information
 | 
						|
 | 
						|
Recompile Asterisk and install the new version.
 | 
						|
 | 
						|
 | 
						|
7) Once you've recompiled and re-installed Asterisk, check to make sure
 | 
						|
res_odbc.so has been compiled.
 | 
						|
 | 
						|
localhost*CLI> show modules like res_odbc.so
 | 
						|
Module                         Description                              Use Count 
 | 
						|
res_odbc.so                    ODBC Resource                            0         
 | 
						|
1 modules loaded
 | 
						|
 | 
						|
 | 
						|
8) Now it's time to get Asterisk configured.  First, we need to tell Asterisk
 | 
						|
about our ODBC setup.  Open /etc/asterisk/res_odbc.conf and add the following:
 | 
						|
 | 
						|
[postgres]
 | 
						|
enabled => yes
 | 
						|
dsn => testing
 | 
						|
pre-connect => yes
 | 
						|
 | 
						|
9) At the Asterisk CLI, unload and then load the res_odbc.so module.  (You
 | 
						|
could restart Asterisk as well, but this way makes it easier to tell what's
 | 
						|
happening.)  Notice how it says it's connected to "postgres", which is our ODBC
 | 
						|
connection as defined in res_odbc.conf, which points to the "testing" DSN in
 | 
						|
ODBC.
 | 
						|
 | 
						|
localhost*CLI> unload res_odbc.so
 | 
						|
Jan  2 21:19:36 WARNING[8130]: res_odbc.c:498 odbc_obj_disconnect: res_odbc: disconnected 0 from postgres [testing]
 | 
						|
Jan  2 21:19:36 NOTICE[8130]: res_odbc.c:589 unload_module: res_odbc unloaded.
 | 
						|
localhost*CLI> load res_odbc.so
 | 
						|
 Loaded /usr/lib/asterisk/modules/res_odbc.so => (ODBC Resource)
 | 
						|
  == Parsing '/etc/asterisk/res_odbc.conf': Found
 | 
						|
Jan  2 21:19:40 NOTICE[8130]: res_odbc.c:266 load_odbc_config: Adding ENV var: INFORMIXSERVER=my_special_database
 | 
						|
Jan  2 21:19:40 NOTICE[8130]: res_odbc.c:266 load_odbc_config: Adding ENV var: INFORMIXDIR=/opt/informix
 | 
						|
Jan  2 21:19:40 NOTICE[8130]: res_odbc.c:295 load_odbc_config: registered database handle 'postgres' dsn->[testing]
 | 
						|
Jan  2 21:19:40 NOTICE[8130]: res_odbc.c:555 odbc_obj_connect: Connecting postgres
 | 
						|
Jan  2 21:19:40 NOTICE[8130]: res_odbc.c:570 odbc_obj_connect: res_odbc: Connected to postgres [testing]
 | 
						|
Jan  2 21:19:40 NOTICE[8130]: res_odbc.c:600 load_module: res_odbc loaded.
 | 
						|
 | 
						|
You can also check the status of your ODBC connection at any time from the
 | 
						|
Asterisk CLI:
 | 
						|
 | 
						|
localhost*CLI> odbc show
 | 
						|
Name: postgres 
 | 
						|
DSN: testing
 | 
						|
Connected: yes
 | 
						|
 | 
						|
10) Now we can setup our voicemail table in PostgreSQL.  Log into PostgreSQL and
 | 
						|
type (or copy and paste) the following:
 | 
						|
 | 
						|
--
 | 
						|
-- First, let's create our large object type, called "lo"
 | 
						|
--
 | 
						|
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;
 | 
						|
 | 
						|
--
 | 
						|
-- If we're not already using plpgsql, then let's use it!
 | 
						|
--
 | 
						|
CREATE TRUSTED LANGUAGE plpgsql;
 | 
						|
 | 
						|
--
 | 
						|
-- Next, let's create a trigger to cleanup the large object table
 | 
						|
-- whenever we update or delete a row from the voicemessages table
 | 
						|
--
 | 
						|
 | 
						|
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;
 | 
						|
 | 
						|
--
 | 
						|
-- Now, let's create our voicemessages table
 | 
						|
-- This is what holds the voicemail from Asterisk
 | 
						|
--
 | 
						|
 | 
						|
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
 | 
						|
);
 | 
						|
 | 
						|
--
 | 
						|
-- Let's not forget to make the voicemessages table use the trigger
 | 
						|
--
 | 
						|
 | 
						|
CREATE TRIGGER vm_cleanup AFTER DELETE OR UPDATE ON voicemessages FOR EACH ROW EXECUTE PROCEDURE vm_lo_cleanup();
 | 
						|
 | 
						|
 | 
						|
11) Just as a sanity check, make sure you check the voicemessages table via the
 | 
						|
isql utility.
 | 
						|
 | 
						|
[jsmith2@localhost ODBC]$ echo "SELECT id, msgnum, dir, duration FROM voicemessages WHERE msgnum = 1" | isql testing
 | 
						|
+---------------------------------------+
 | 
						|
| Connected!                            |
 | 
						|
|                                       |
 | 
						|
| sql-statement                         |
 | 
						|
| help [tablename]                      |
 | 
						|
| quit                                  |
 | 
						|
|                                       |
 | 
						|
+---------------------------------------+
 | 
						|
SQL> +------------+------------+---------------------------------------------------------------------------------+---------------------+
 | 
						|
| id         | msgnum     | dir                                                                             | duration            |
 | 
						|
+------------+------------+---------------------------------------------------------------------------------+---------------------+
 | 
						|
+------------+------------+---------------------------------------------------------------------------------+---------------------+
 | 
						|
SQLRowCount returns 0
 | 
						|
 | 
						|
 | 
						|
12) Now we can finally configure voicemail in Asterisk to use our database.
 | 
						|
Open /etc/asterisk/voicemail.conf, and look in the [general] section.  I've
 | 
						|
changed the format to gsm (as I can't seem to get WAV or wav working), and
 | 
						|
specify both the odbc connection and database table to use.
 | 
						|
 | 
						|
[general]
 | 
						|
; Default formats for writing Voicemail
 | 
						|
;format=g723sf|wav49|wav
 | 
						|
format=gsm
 | 
						|
odbcstorage=postgres
 | 
						|
odbctable=voicemessages
 | 
						|
 | 
						|
You'll also want to create a new voicemail context called "odbctest" to do some
 | 
						|
testing, and create a sample mailbox inside that context.  Add the following to
 | 
						|
the very bottom of voicemail.conf:
 | 
						|
 | 
						|
[odbctest]
 | 
						|
101 => 5555,Example Mailbox
 | 
						|
 | 
						|
 | 
						|
13) Once you've updated voicemail.conf, let's make the changes take effect:
 | 
						|
 | 
						|
localhost*CLI> unload app_voicemail.so
 | 
						|
  == Unregistered application 'VoiceMail'
 | 
						|
  == Unregistered application 'VoiceMailMain'
 | 
						|
  == Unregistered application 'MailboxExists'
 | 
						|
  == Unregistered application 'VMAuthenticate'
 | 
						|
localhost*CLI> 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
 | 
						|
 | 
						|
You can check to make sure your new mailbox exists by typing:
 | 
						|
 | 
						|
localhost*CLI> show voicemail users for odbctest 
 | 
						|
Context    Mbox  User                      Zone       NewMsg
 | 
						|
odbctest   101   Example Mailbox                           0
 | 
						|
 | 
						|
 | 
						|
14) Now, let's add a new context called "odbc" to extensions.conf.  We'll use
 | 
						|
these extensions to do some testing:
 | 
						|
 | 
						|
[odbc]
 | 
						|
exten => 100,1,Voicemail(101@odbctest)
 | 
						|
exten => 200,1,VoicemailMain(101@odbctest)
 | 
						|
 | 
						|
 | 
						|
15) Next, we need to point a phone at the odbc context.  In my case, I've got a
 | 
						|
SIP phone called "linksys" that is registering to Asterisk, so I'm setting its
 | 
						|
context to the [odbc] context we created in the previous step.  The relevant
 | 
						|
section of my sip.conf file looks like:
 | 
						|
 | 
						|
[linksys]
 | 
						|
type=friend
 | 
						|
secret=verysecret
 | 
						|
disallow=all
 | 
						|
allow=ulaw
 | 
						|
allow=gsm
 | 
						|
context=odbc
 | 
						|
host=dynamic
 | 
						|
qualify=yes
 | 
						|
 | 
						|
I can check to see that my linksys phone is registered with Asterisk correctly:
 | 
						|
 | 
						|
localhost*CLI> sip show peers like linksys
 | 
						|
Name/username              Host            Dyn Nat ACL Port     Status    
 | 
						|
linksys/linksys            192.168.0.103    D          5060     OK (9 ms) 
 | 
						|
1 sip peers [1 online , 0 offline]
 | 
						|
 | 
						|
 | 
						|
16) At last, we're finally ready to leave a voicemail message and have it
 | 
						|
stored in our database!  (Who'd have guessed it would be this much trouble?!?)
 | 
						|
Pick up the phone, dial extension 100, and leave yourself a voicemail message.
 | 
						|
In my case, this is what appeared on the Asterisk CLI:
 | 
						|
 | 
						|
localhost*CLI> 
 | 
						|
    -- Executing VoiceMail("SIP/linksys-10228cac", "101@odbctest") in new stack
 | 
						|
    -- Playing 'vm-intro' (language 'en')
 | 
						|
    -- Playing 'beep' (language 'en')
 | 
						|
    -- Recording the message
 | 
						|
    -- x=0, open writing:  /var/spool/asterisk/voicemail/odbctest/101/tmp/dlZunm format: gsm, 0x101f6534
 | 
						|
    -- User ended message by pressing #
 | 
						|
    -- Playing 'auth-thankyou' (language 'en')
 | 
						|
  == Parsing '/var/spool/asterisk/voicemail/odbctest/101/INBOX/msg0000.txt': Found
 | 
						|
 | 
						|
Now, we can check the database and make sure the record actually made it into
 | 
						|
PostgreSQL, from within the psql utility.
 | 
						|
 | 
						|
[jsmith2@localhost ~]$ psql
 | 
						|
Password: 
 | 
						|
Welcome to psql 8.1.4, the PostgreSQL interactive terminal.
 | 
						|
 | 
						|
Type:  \copyright for distribution terms
 | 
						|
       \h for help with SQL commands
 | 
						|
       \? for help with psql commands
 | 
						|
       \g or terminate with semicolon to execute query
 | 
						|
       \q to quit
 | 
						|
 | 
						|
asterisk=# SELECT * FROM voicemessages;
 | 
						|
 id | msgnum |                       dir                        | context | macrocontext |       callerid        |  origtime  | duration | mailboxuser | mailboxcontext | recording | label | read | sip_id | pabx_id | iax_id 
 | 
						|
----+--------+--------------------------------------------------+---------+--------------+-----------------------+------------+----------+-------------+----------------+-----------+-------+------+--------+---------+--------
 | 
						|
 26 |      0 | /var/spool/asterisk/voicemail/odbctest/101/INBOX | odbc    |              | "linksys" <linksys> | 1167794179 | 7        | 101         | odbctest       | 16599     |       | f    |        |         |       
 | 
						|
(1 row)
 | 
						|
 | 
						|
Did you notice the the recording column is just a number?  When a recording
 | 
						|
gets stuck in the database, the audio isn't actually stored in the
 | 
						|
voicemessages table.  It's stored in a system table called the large object
 | 
						|
table.  We can look in the large object table and verify that the object
 | 
						|
actually exists there:
 | 
						|
 | 
						|
asterisk=# \lo_list
 | 
						|
    Large objects
 | 
						|
  ID   | Description 
 | 
						|
-------+-------------
 | 
						|
 16599 | 
 | 
						|
(1 row)
 | 
						|
 | 
						|
In my case, the OID is 16599.  Your OID will almost surely be different.  Just
 | 
						|
make sure the OID number in the recording column in the voicemessages table
 | 
						|
corresponds with a record in the large object table.  (The trigger we added to
 | 
						|
our voicemessages table was designed to make sure this is always the case.)
 | 
						|
 | 
						|
We can also pull a copy of the voicemail message back out of the database and
 | 
						|
write it to a file, to help us as we debug things:
 | 
						|
 | 
						|
asterisk=# \lo_export 16599 /tmp/odcb-16599.gsm
 | 
						|
lo_export
 | 
						|
 | 
						|
We can even listen to the file from the Linux command line:
 | 
						|
 | 
						|
[jsmith2@localhost tmp]$ play /tmp/odcb-16599.gsm
 | 
						|
 | 
						|
Input Filename : /tmp/odcb-16599.gsm
 | 
						|
Sample Size    : 8-bits
 | 
						|
Sample Encoding: gsm
 | 
						|
Channels       : 1
 | 
						|
Sample Rate    : 8000
 | 
						|
 | 
						|
Time: 00:06.22 [00:00.00] of 00:00.00 (  0.0%) Output Buffer: 298.36K
 | 
						|
 | 
						|
Done.
 | 
						|
 | 
						|
 | 
						|
17) Last but not least, we can pull the voicemail message back out of the
 | 
						|
database by dialing extension 200 and entering "5555" at the password prompt.
 | 
						|
You should see something like this on the Asterisk CLI:
 | 
						|
 | 
						|
localhost*CLI> 
 | 
						|
    -- Executing VoiceMailMain("SIP/linksys-10228cac", "101@odbctest") 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/odbctest/101/INBOX/msg0000.txt': Found
 | 
						|
    -- Playing 'vm-received' (language 'en')
 | 
						|
    -- Playing 'digits/at' (language 'en')
 | 
						|
    -- Playing 'digits/10' (language 'en')
 | 
						|
    -- Playing 'digits/16' (language 'en')
 | 
						|
    -- Playing 'digits/p-m' (language 'en')
 | 
						|
    -- Playing '/var/spool/asterisk/voicemail/odbctest/101/INBOX/msg0000' (language 'en')
 | 
						|
    -- Playing 'vm-advopts' (language 'en')
 | 
						|
    -- Playing 'vm-repeat' (language 'en')
 | 
						|
    -- Playing 'vm-delete' (language 'en')
 | 
						|
    -- Playing 'vm-toforward' (language 'en')
 | 
						|
    -- Playing 'vm-savemessage' (language 'en')
 | 
						|
    -- Playing 'vm-helpexit' (language 'en')
 | 
						|
    -- Playing 'vm-goodbye' (language 'en')
 | 
						|
 | 
						|
That's it!
 | 
						|
 | 
						|
Jared Smith
 | 
						|
2 Jan 2006
 | 
						|
(updated 11 Mar 2007)
 |