Monday, July 9, 2012

Asterisk behind Kamailio & Voicemail MWI

Once again my workaholic nature didn't let me rest this Saturday and Sunday as I kept thinking about how can the MWI indications work with Asterisk as voicemail server behind a Kamailio server. Kamailio is the REGISTRAR of SIP users.

Since SIP users register on Kamailio, so Asterisk won't trigger a NOTIFY on it's voice-message recording.

There are many methods discussed on voip-info.org page. Among the other which weren't working or required patching I worked on manual SUBSCRIBE-NOTIFY triggering method by "Andreas Granig" which is openly discussed and shared on this mailing-list post in 2004. I haven't modified anything except the IP addresses for my servers.



Since a picture is worth a thousand words so see the attached image below for the logical flow of the solution.

Kamailio and Asterisk with MWI working

Since the time the solution was posted a lot of things have changed with SER and Kamailio. I am using Kamailio 3.3 so I had to change a few variables and minor things to make this work for me.


In Kamialio configurations File
if (is_method("NOTIFY") & ds_is_from_list()) {
                route(LOCATION);
                if (!t_relay()) {
                        sl_reply_error();
                        break;
                };
};

if (method=="SUBSCRIBE") {
        # challenge/response
                if (!www_authorize("$td", "asv_sip")) {
                        www_challenge("$td", "0");
                        break;
                };
        # notify the Asterisk server to update the UAs MWI. I
        # believe most UAs that support SUBSCRIBE will
        # periodically send a SUBSCRIBE message. These cause
        # the Asterisk server to update their MWI.
            
   exec_avp("subscribe sip:$rU@192.168.2.181","$avp(s:test)");
                           sl_send_reply("200","OK");
                break;
};

where subscribe is a scrip placed in directory /usr/bin/mwi 
A symbolic link of the subscribe script is created in /usr/sbin directory as well.

--------------------------------
#!/bin/sh

# this script simply accepts as input the subscriber URI such
# as sip:1001 at sip01.mycompany.com and "touches" a file with the
# same name. It stores the file in /var/spool/mwi/immediate
# which is later processed in a CRON job

USER=$1
# some input arguments may contain ";user=phone" so we need to
# strip it off. We also need to strip of the "sip:" piece so that
# all we are left with is "1001 at sip.mycompany.com"

IDX1=`expr index "$1" ":"`
IDX1=$(($IDX1 + 1))
IDX2=`expr index "$1" ";"`

if [ $IDX2 != 0 ]; then
  IDX2=$(($IDX2 - $IDX1))
  USER=`expr substr $1 $IDX1 $IDX2`
fi
if [ $IDX2 = 0 ]; then
  USER=`expr substr $1 $IDX1 80`
fi
echo "touch /var/spool/mwi/$USER"
touch /var/spool/mwi/$USER
--------------------------------


Once this scripts create a file for the user requesting Voicemail Notify the next script cron-subscribe transports the username to the Asterisk server which is saving the voicemails for that user. The subscribe and the cron-subcribe scripts can be merged into just one script as well.


cron-subscribe
--------------------------------
#!/bin/sh


# this script uses scp to copy the files from /var/spool/mwi/immediate
# to the Asterisk server. It is important to note that the scp works
# without being prompted for a password.
MSG_HOME=/var/spool/mwi
VM_HOME=/var/spool/mwi/immediate
for file in $MSG_HOME/*
do
  if [ -f $file ]; then
        echo $file
    scp $file 192.168.2.86:$VM_HOME
    rm -f $file
  fi
done
--------------------------------
Don;t forget to create the directory:

root#mkdir -p /var/spool/mwi/

Set in cronjob
* * * * * /usr/bin/mwi/cron-subscribe


IN ASTERISK SERVER/VOICEMAIL DIRECTORY SERVER

root#yum install sipsak

Create the following scripts in Asterisk server directory ::  /usr/bin/mwi
Don't forget to change the IP addresses accordingly in your setup.

---------------------
NOTIFY sip:SUBSCRIBER SIP/2.0
Via: SIP/2.0/UDP 192.168.2.86:5060
Max-Forwards: 70
Route: <sip:SUBSCRIBER>
From: "VM-MWI" <sip:VM-MWI@192.168.2.86:5060>
To: <sip:SUBSCRIBER>
Contact: <sip:VM-MWI@192.168.2.86>
Call-ID: 29320a42d73ae512NWM4OTc4ODI0YmVlNjJkMjQ1MmJmYTYyOGFlNDMzY2Y.
CSeq: 103 NOTIFY
User-Agent: Vocemail-MWI Server
Event: message-summary
Content-Type: application/simple-message-summary
Subscription-State: active
Content-Length: CONTENT_LENGTH

Messages-Waiting: HAS_MESSAGE
Message-Account: sip:*97@mysipserver.mycompany.com
Voice-Message: NEW_COUNT/OLD_COUNT (0/0)
-----------------------

notify.msg is the sample file which will get called in cron-mwi-immediate file and all variables will be replaced there and this NOTIFY will be sent out to Kamailio server.


-----------------------
#!/bin/sh

# this script "touches" files in the spool directory which a
# cron jobs later processes. The cron job is where we actually
# send the SIP NOTIFY back to the UAs
CONTEXT=$1
EXTENSION=$2
VM_COUNT=$3

touch /var/spool/mwi/immediate/$EXTENSION
-------------------

mwi-immediate script is used from within the dialplan. Once we receive a voicemail we can execute this script from asterisk dialplan to send out a NOTIFY after receiving the message.

-------------------
#!/bin/sh

# This script sends the SIP NOTIFY message to UAs. The NOTIFY message
# can either enable or disable the UA message indicator. The script
# looks for any file in $VM_HOME and creates the NOTIFY message via
# the template in $TEMPLATE.
#
# The actual NOTIFY message is sent to the SIP proxy by the sipsak
# utilty. After the message is send we delete the processed file
# from VM_HOME so we don't keep resending the same message. Most
# UAs will periodically send a SUBSCRIBE message to the SIP proxy.
# When this occurs the SIP proxy will place a file in the location
# /var/spool/mwi/delay and a less frequent CRON job will process it.

                                                                        #
# ALSO NOTE: I am not using the Asterisk "telnet API". I can't
# remember what it's called, but I think looking for voice mail
# files on disk is quicker. I do not know if this is 100% safe since
# I am totally bypassing Asterisk in order to determine if a user has
# new voice mail messages. Can anyone verify this as a good way to
# handle this?

VM_ROOT=/var/spool/asterisk/voicemail/default
VM_HOME=/var/spool/mwi/immediate
TEMPLATE=/usr/bin/mwi/notify.msg


cd $VM_HOME


for file in *
do
        #echo $file
        LEN=`expr length $file`
        IDX1=`expr index "$file" \@`
        IDX2=$(($IDX1 + 1))
        IDX1=$(($IDX1 - 1))
        MAILBOX=`expr substr $file 1 $IDX1`
        DOMAIN=`expr substr $file $IDX2 $LEN`
        TOTAL_MESSAGES=`find $VM_ROOT/$MAILBOX -name "*.txt" |
                        wc -l | sed 's/^ *\(.*\) *$/\1/'`
#       echo $TOTAL_MESSAGES
        NEW_MESSAGES=`find $VM_ROOT/$MAILBOX/INBOX -name "*.txt" |
                        wc -l | sed 's/^ *\(.*\) *$/\1/'`
        #echo $NEW_MESSAGES
        OLD_MESSAGES=$(($TOTAL_MESSAGES - $NEW_MESSAGES))
        #echo $OLD_MESSAGES


        if [ "$NEW_MESSAGES" == "0" ]; then
                HAS_NEW="no"
        else
                HAS_NEW="yes"
        fi


        CONTENT_LENGTH=$((84 + `expr length $HAS_NEW` +
                               `expr length $NEW_MESSAGES` +
                               `expr length $OLD_MESSAGES`))

        #echo $CONTENT_LENGTH

        CMD="s/SUBSCRIBER/$file/g;s/MAILBOX/$MAILBOX/g;s/CONTENT_LENGTH/$CONTENT_LENGTH/g;s/HAS_MESSAGE/$HAS_NEW/g;s/NEW_COUNT/$NEW_MESSAGES/g;s/OLD_COUNT/$OLD_MESSAGES/g"


        cat /usr/bin/mwi/notify.msg | sed -e $CMD > /tmp/mwi-immediate


        #echo "Notifying $file"
        `sipsak shoot -a number4 -f /tmp/mwi-immediate -s sip:$file`


       rm $file
done
----------------------------------------------------

This cron-mwi-immediate is the heart of the logic. This script search the directories of the required user and if there are newer messgaes waiting then it takes in the file "notify.msg" and fill in the variables accordingly and shoot out the NOTIFY towards the Kamailio server.

This NOTIFY is already handled in kamailio configuration.

root#mkdir -p /var/spool/mwi/immediate

Set in crontab
 * * * * /usr/bin/mwi/cron-mwi-immediate

Thats all... now you'r MWI should work. :)

Hope this was a helpful post for the needy one's.

REF LINK: http://lists.iptel.org/pipermail/serusers/2004-September/011552.html
http://www.voip-info.org/wiki/view/Asterisk+at+large


20 comments:

  1. im trying to do the same job but i get below errors on the log:
    Jul 13 16:57:04 testbox openser[10863]: ERROR:parse_first_line: bad message
    Jul 13 16:57:04 testbox openser[10863]: ERROR: parse_msg: message=;tag=as142bf0fbCRLF #015#012To: #015#012Contact: #015#012Call-ID: 102@195.74.114.238 #015#012CSeq: 102 NOTIFY #015#012User-Agent:Vmail Server #015#012Max-Forwards: 70 #015#012Event: message-summary #015#012Content-Type: application/simple-message-summary #015#012Content-Length: 64#015#012 #015#012Messages-Waiting: yes #015#012#015#012#015#012>
    Jul 13 16:57:04 testbox openser[10863]: ERROR: receive_msg: parse_msg failed
    Jul 13 16:57:08 testbox openser[10860]: ERROR:parse_first_line: bad request first line
    Jul 13 16:57:08 testbox openser[10860]: ERROR: at line 0 char 36:
    Jul 13 16:57:08 testbox openser[10860]: ERROR: parsed so far: NOTIFY sip:502@195.74.114.238:5060
    Jul 13 16:57:08 testbox openser[10860]: ERROR:parse_first_line: bad message
    here is the notify.txt file:
    NOTIFY sip:502@195.74.114.238:5060
    From: "Vmail Server" ;tag=as142bf0fbCRLF
    To:
    Contact:
    Call-ID: 102@195.74.114.238
    CSeq: 102 NOTIFY
    User-Agent:Vmail Server
    Max-Forwards: 70
    Event: message-summary
    Content-Type: application/simple-message-summary
    Content-Length: 64

    Messages-Waiting: yes

    ReplyDelete
  2. Your Notify message has no "To" header set , then I can't really tell why you don't want the VIA header. The FROM header is again distorted.. Contact header is empty !!

    Please explain a bit more about your scenario .

    See Notify.msg ==> [URL:: http://pastebin.pk/9]

    ReplyDelete
  3. how can we run this script every 5 minutes in asterisk?

    ReplyDelete
  4. We can't schedule things IN asterisk, use crontab for this

    */5 * * * * /path/to/myscript.sh

    http://kevin.vanzonneveld.net/techblog/article/schedule_tasks_on_linux_using_crontab/

    ReplyDelete
  5. Hi,
    ive just faced an Asterisk issue: Autodestruct on dialog … with owner in place (Method: BYE) and i wonder if you can help .asterisk doesn't hangup after endusers hang up.i set the debug on.asterisk receives a buy and transmits a 200ok in response but it seems the endure doest receive that.
    below is my openser.cfg file:

    if (!method=="REGISTER")
    record_route();


    if (loose_route()) {

    append_hf("P-hint: rr-enforced\r\n");
    route(1);

    };

    if (!uri==myself) {


    };

    if(lookup("location") && (src_ip=="195.74.114.238")) {
    log(1,"From Asterisk 1");
    record_route();
    route(1);
    exit;
    }
    if (method=="NOTIFY") {
    log(1, "NOTIFY Method found\n");
    if(!t_relay()){
    sl_reply_error();
    log(1, "t_relay failed\n" );
    }
    else {
    log(1, "t_relay success\n" );
    }

    }




    if (method=="NOTIFY" & src_ip=="195.74.114.238") {
    log(1,"NOTIFY recevied");
    record_route();
    if (!t_relay()) {

    sl_reply_error();
    return;
    };
    };

    if (method=="SUBSCRIBE") {

    if (!www_authorize("openser.org", "subscriber")) {
    www_challenge("openser.org", "0");
    return;
    };

    }

    if((src_ip=="195.74.114.238")) {
    log(1,"From Asterisk 2");
    record_route();
    route(1);
    exit;
    }
    if(method=="INVITE") {

    route(3);
    exit;
    }

    if(method=="ACK") {
    record_route();
    route(3);
    exit;
    }

    if(method=="BYE"|| method=="CANCEL") {


    end_media_session();

    }





    if (uri==myself) {

    if (method=="REGISTER") {


    if (!www_authorize("openser.org", "subscriber")) {
    www_challenge("openser.org", "0");
    exit;
    };

    save("location");
    exit;
    };

    lookup("aliases");
    if (!uri==myself) {
    append_hf("P-hint: outbound alias\r\n");
    #route(1);
    };


    if (!lookup("location")) {
    sl_send_reply("404", "Not Found");
    exit;
    };
    append_hf("P-hint: usrloc applied\r\n");
    };

    ;route(1);
    }


    route[1] {

    log(1,"Hit route 1");
    t_on_reply("1");
    if (!t_relay()) {
    if (method=="INVITE" || method=="ACK") {

    end_media_session();
    };
    sl_reply_error();
    };
    exit;
    }

    route[3] {
    log(1,"Hit trusted");
    if (!proxy_authorize("","subscriber")) {
    proxy_challenge("","0");
    exit;
    };


    log(1,"Send to asterisk");
    rewritehostport("195.74.114.238:5062");
    t_relay();

    }


    thanks

    ReplyDelete
    Replies
    1. 1-
      I'm kind of in a fix here, the code is un-indented and hence its really poor for me to read. Please use some pastebin to out your code and paste here.
      2-
      Though I'd love to help you here but just for your sake paste your issues on opensips/kamailio users-list where all the experts can help you at any time.

      3-
      I haven't been able to go through your code but can you confirm that your connected calls do not disconnect after 30 sec !? if 200 OK of INVITE reaches then BYE's 200 OK should get routed as-well EXCEPT you've not handled BYE in the same way!

      Delete
  6. This solution isn't dynamic nor scalable!

    ReplyDelete
    Replies
    1. I agree with you. But given this one possible approach we can always create a better scalable solution. I still thank the real guy who posted this solution some years back and atleast it still works.

      Delete
  7. This comment has been removed by the author.

    ReplyDelete
  8. after executing: "/usr/bin/mwi/cron-mwi-immediate", I found the script "/tmp/mwi-immediate" is replaced to be same as the file: notify.msg !

    also I got this error:

    root@192:~# /usr/bin/mwi/cron-mwi-immediate
    [: 74: 1: unexpected operator
    /usr/bin/mwi/cron-mwi-immediate: 74: '*': not found
    rm: cannot remove `*': No such file or directory

    ReplyDelete
  9. after executing: "/usr/bin/mwi/cron-mwi-immediate", I found the script "/tmp/mwi-immediate" is replaced to be same as the file: notify.msg !

    also I got this error:

    root@192:~# /usr/bin/mwi/cron-mwi-immediate
    [: 74: 1: unexpected operator
    /usr/bin/mwi/cron-mwi-immediate: 74: '*': not found
    rm: cannot remove `*': No such file or directory

    ReplyDelete
  10. after executing: "/usr/bin/mwi/cron-mwi-immediate", I found the script "/tmp/mwi-immediate" is replaced to be same as the file: notify.msg !

    also I got this error:

    root@192:~# /usr/bin/mwi/cron-mwi-immediate
    [: 74: 1: unexpected operator
    /usr/bin/mwi/cron-mwi-immediate: 74: '*': not found
    rm: cannot remove `*': No such file or directory

    ReplyDelete
    Replies
    1. This might mean that you need to check the asterisk Voicemial directory and adjust the script according to your directory tree.

      Delete
  11. Hi all,
    I am stuck on exec_avp is not working in kamailio.Its properly installed .It only executed the logger command.It is not executing any other system command.

    Regards,
    Ansuman

    ReplyDelete
  12. Hi All,

    In my case exec_avp is not behaving well.Although it is installed properly.It is not executing any system commands except logger command.

    Regards,
    ansuman

    ReplyDelete
    Replies
    1. Check the permissions with which your kamailio is running, change the permission of your script to kamailio permissions and see if that executes.

      Delete
  13. Hi all,

    One thing is there,kamailio is not handling notify from asterisk.How to do it?
    Need help

    ReplyDelete
  14. I gave all root privileges to the user.But no luck.Any other solution

    ReplyDelete
  15. How to handle the notify coming from asterisk?if i have to use some kind of modules like pua for this?

    Regards,
    Ansuman

    ReplyDelete
  16. This comment has been removed by the author.

    ReplyDelete