Table of Contents

This is a tutorial on how to use OpenSER with Asterisk functioning as a voicemail server.

Create the Database Views

First, create the views described in OpenSER to Asterisk Realtime MySQL Views. This allows Asterisk to use the OpenSER's user table, so you don't have to manually replicate them.

Modify Asterisk Codebase

Next, you will need to modify the asterisk code base. This is unfortunate, but otherwise you will be unable to update the voicemail passwords.

In asterisk-1.2.0/apps/app_voicemail.c, struct ast_vm_user should have a uniqueid size of 128. Like this:

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[128];              /*!< Unique integer identifier */
        char exit[80];
        unsigned int flags;             /*!< VM_ flags */
        int saydurationm;
        int maxmsg;                     /*!< Maximum number of msgs per folder for this mailbox */
        struct ast_vm_user *next;
};

Install Asterisk Add-ons

Now, download the latest Asterisk addons package from the website. Compile and install both asterisk packages.

Configure Asterisk Realtime

Now, it is time to specify where Asterisk should look for its realtime data. In /etc/asterisk/extconfig.conf, you will need to have:

[settings]
sipusers => mysql,asterisk,sip
sippeers => mysql,asterisk,sip
voicemail => mysql,asterisk,voicemail

Now you will need to specify how to connect asterisk. In /etc/asterisk/res_mysql.conf insert:

[general]
dbhost = localhost
dbname = asterisk
dbuser = asterisk
dbpass = asterisk
dbport = 3306

This assumes that the user asterisk@localhost with password asterisk will be allowed to access the asterisk database. The code for adding this user is:

GRANT ALL ON asterisk.* to asterisk@localhost IDENTIFIED BY 'asterisk';

Configure Asterisk SIP

Allow sip calls in your sip.conf file (/etc/asterisk/sip.conf):

[general]
context=default		; Default context for incoming calls
allowguest=yes			; Allow or reject guest calls (default is yes, this can also be set to 'osp'
bindport=5060			; UDP Port to bind to (SIP standard port is 5060)
bindaddr=0.0.0.0		; IP address to bind to (0.0.0.0 binds to all)
srvlookup=yes			; Enable DNS SRV lookups on outbound calls
;domain=voicemail		; Set default domain for this host
;domain=mydomain.tld,mydomain-incoming
;domain=127.0.0.1			; Add IP address as local domain
;allowexternalinvites=no	; Disable INVITE and REFER to non-local domains
autodomain=yes			; Turn this on to have Asterisk add local host
pedantic=yes			; Enable slow, pedantic checking for Pingtel
;tos=184			; Set IP QoS to either a keyword or numeric val
;tos=lowdelay			; lowdelay,throughput,reliability,mincost,none
;maxexpiry=3600			; Max length of incoming registration we allow
;defaultexpiry=120		; Default length of incoming/outoing registration
;notifymimetype=text/plain	; Allow overriding of mime type in MWI NOTIFY
checkmwi=10			; Default time between mailbox checks for peers
vmexten=default     		; dialplan extension to reach mailbox sets the 
;videosupport=yes		; Turn on support for SIP video
;recordhistory=yes		; Record SIP history by default 
disallow=all			; First disallow all codecs
allow=ulaw			; Allow codecs in order of preference
language=en			; Default language setting for all users/peers
;rtpholdtimeout=300		; Terminate call if 300 seconds of no RTP activity
;trustrpid = no			; If Remote-Party-ID should be trusted
;sendrpid = yes			; If Remote-Party-ID should be sent
useragent=Asterisk PBX		; Allows you to change the user agent string
;dtmfmode = rfc2833		; Set default dtmfmode for sending DTMF. Default: rfc2833
				; Other options: 
				; info : SIP INFO messages
				; inband : Inband audio (requires 64 kbit codec -alaw, ulaw)
				; auto : Use rfc2833 if offered, inband otherwise

[openser]
type=friend
context=default
host=localhost
insecure=very

Make sure to substitute the correct host for openser where it says host=localhost, or, if you are running OpenSER and Asterisk on the same host, change the port setting so that these servers will not conflict.

Configure Voicemail

Edit your voicemail.conf to how you like it. You should remove all default user and other user accounts listed there.

Configure Extensions

This is the equivalent of OpenSER's route{}. Here is a sample extensions file to send incoming calls to voicemail:

[general]
static=yes
writeprotect=no
autofallthrough=yes
clearglobalvars=no
priorityjumping=no

[globals]
CONSOLE=Console/dsp                             ; Console interface for demo

[default]
exten => _XXXXXXXXXXX,1,Voicemail(su${EXTEN})
exten => _XXXXXXXXXXX,2,Hangup

exten => a,1,VoicemailMain(${EXTEN})
exten => a,2,Playback(Goodbye)
exten => a,3,Hangup

exten => *86,1,VoicemailMain(${CALLERIDNUM})
exten => *86,2,Hangup

Set OpenSER to Route Calls to Voicemail

In your openser.cfg, you will need to have it route calls to asterisk. This is usually done with some kind of failure route. Of some importance is making sure that calls timeout sooner than the default, 200 seconds. Here is a basic route. This is very new, and seems to work, but more testing is needed. You mileage may vary.

#tm timeout for voicemail params
modparam("tm", "fr_timer", 3)
modparam("tm", "fr_inv_timer", 35)
modparam("tm", "noisy_ctimer", 1)

# -------------------------  request routing logic -------------------

# main routing logic
#Routing Script
route {
 #check for old messages: could mean a problem with the DNS entries or some other loop-causer...
 if (!mf_process_maxfwd_header("10"))
 {
   xlog("L_WARN", "WARNING: Too many hops\n");
   sl_send_reply("483", "Too many hops, forward count exceeded limit\n");
   return;
 };
  
 #check for extremely large messages; we don't need a sip dos attack
 if (msg:len >= 2048)
 {
   xlog("L_WARN", "WARNING: Message too large, >= 2048 bytes\n");
   sl_send_reply("513", "Message too large, exceeded limit\n");
   return;
 };
  
 #record everything besides registers and acks
 if(method!="REGISTER" && method!="ACK")
 {
  setflag(1);
 }; 
  
 #do not send to voicemail if BYE or CANCEL 
 #is used to end call before user pickup or timeout
 if(method=="CANCEL" || method=="BYE")
 {
  setflag(10);
 };
 
 #grant route if route headers already present
 if (loose_route()) 
 {  
   route(1);
   return;
 };
  
 #Always require authentication, which could result in a PSTN, ie $$$
 if (method=="REGISTER")
 {
   if(!www_authorize("domain.net", "subscriber"))
   {
    www_challenge("domain.net", "0");
    return;
   }
   else
   {
      if (!check_to()) 
      {
	sl_send_reply("401", "Unauthorized");
	return;
      };

    #Save into user database, used below when checking if user is available
    xlog("L_INFO", "REGISTER: User Authenticated Correctly\n");
    save("location");
    return;
   };
 };
  
 if (method=="INVITE")
 {
  if(uri=~"sip:\*86@.*")
  {
   #authorize if a call is going to PSTN 
   if(!proxy_authorize("domain.net", "subscriber"))
   {
    proxy_challenge("domain.net", "0");
    return;
   };
 
   xlog("L_INFO", "CALL: Call to check voicemail\n");
   rewritehostport("vmail.domain.net:5060");
  }
  else
  {
   if (does_uri_exist())
   {
    #Call is to sip client, so do nothing but route
    xlog("L_INFO", "CALL: Sip client\n");
    if (!lookup("location")) 
    {
     sl_send_reply("404", "Not Found");
     log(1, "ERROR: User Not Found\n");
     return;
    };
   }
   else
   {
    #authorize if a call is going to PSTN 
    if(!proxy_authorize("domain.net", "subscriber"))
    {
     proxy_challenge("domain.net", "0");
     return;
    };
 
    #Call destination is PSTN, so send it to the gateway (Net.com)
    xlog("L_INFO", "CALL: PSTN gateway\n");
    rewritehostport("voip_gw.domain.net:5060");
   };
  };
   
  #Make sure that all subsequent requests go through us;
  record_route();
 }
 else
 {
  if (does_uri_exist())
  {
   #Call is to sip client, so do nothing but route
   xlog("L_INFO", "CALL: Sip client\n");
   if (!lookup("location")) 
   {
    sl_send_reply("404", "Not Found");
    log(1, "ERROR: User Not Found\n");
    return;
   };
  }
  else
  {
   #Call destination is PSTN, so send it to the gateway (Net.com)
   xlog("L_INFO", "CALL: PSTN gateway\n");
   rewritehostport("voip_gw.domain.net:5060");
  };
  record_route();
 };
 
 #ALL PROCESSING IS DONE, SO ROUTE
 route(1);
}


route[1]
{
#send the call outward

 if(method=="INVITE" && !isflagset(10))
 {
  t_on_failure("2");
 };
 
 if (!t_relay()) 
 {
  xlog("L_WARN", "ERROR: t_relay failed");
  sl_reply_error();
 };

}

failure_route[2]
{
 if(!t_was_cancelled())
 {
  revert_uri();
  rewritehostport("vmail.domain.net:5060");
  append_branch();
  #PREVENT SOME CRAZY VOICEMAIL LOOP
  xlog("L_INFO", "INFO: CALL TO VOICEMAIL");
  setflag(10);
  route(1);
 }
}

You can contact me at mikebwilliams@gmail.com

Good Luck!