Grandstream Simple HTTP Configuration Pusher

Here is a simple bash script that logs into the web interface of a Grandstream IP phone and submits some configuration changes, and then reboots the device for the changes to take effect. I wrote this to update a handful of Grandstream phones with a new phonebook XML URL, but it could be used for other configuration changes as well. This was tested with a Grandstream GXP2120 on firmware versions 1.0.1.56, 1.0.1.64, 1.0.1.105, 1.0.4.23, and 1.0.6.7, but might be adaptable for other Grandstream devices as well.

Grandstream has configuration templates for a number of devices. You can download a zip archive of them from the following URL: http://www.grandstream.com/tools/GAPSLITE/config-template.zip. The configuration is made up of a pair of values, a P-code and the P-codes applicable value, Grandstream calls these P values in some of their documentation.

My goal was to get my Grandstream phones to periodically download my automatically generated phonebook file discussed in a previous blog post. The applicable configuration details in my case are shown below.

# Enable Phonebook XML Download. # 0 - No, 1 - YES, HTTP, 2 - YES, TFTP, 3 - Yes, HTTPS. Default is 0
# Number: 0, 1, 2, 3
# Mandatory
#P330 = 0

# Phonebook XML Server Path
# This is a string of up to 256 characters that should contain a path to the XML file. It MUST be in the host/path format.
# For example: directory.grandstream.com/engineering
# String
#P331 =

# Phonebook Download Interval (in minutes)
# Valid value range is 5-720. Default is 0 for disabled
# Number: 0, 5-720;
#P332 = 0

# Remove Manually-edited entries on Download. 0 - No, 1 - Yes. Default is 1
# Number: 0, 1
# Mandatory
#P333 = 1

# Sort Phonebook by. 0 - Last Name. 1 - First Name. Default is 0
# Number: 0, 1
# Mandatory
#P2914 = 0

My Enable Phonebook XML Download method is HTTPS, my Phonebook XML Server Path is “10.0.0.1/phonebook.php“, I want the phone to download on a 30 minute interval and I want it to replace any phone book entries the user might have created.

My applicable P-values were:
P330=3
P331=10.0.0.1/phonebook.php
P332=30
P333=1
I then concatenate these P-values into one long string without any white space using the ampersand as a separator:

P330=3&P331=10.0.0.1/phonebook.php&P332=30&P333=1

Example usage

Here is how I updated a single phone with the P-values discussed earlier. First I set a shell variable containing the values. My device is located on the LAN at 10.0.0.172. I use this IP as the first argument to the script, and the P-values variable in the second argument. If you want to specify the P-values directly to the script without a shell variable, just make sure to quote them so that the ampersands don’t get misinterpreted by your shell. The final argument to the script is the web interface password of the device being updated.

Pvalues="P330=3&P331=10.0.0.1/phonebook.php&P332=30&P333=1"
[root@phone scripts]# ./gs_push.sh 10.0.0.172 $Pvalues admin
Config sent, rebooting...
Saving User Data...Done
Blocks Used ( 119 / 1024 )

Success!

Only one argument (the IP address) is required, the script will ask for the additional data if it isn’t passed in to it from the command line.

[root@phone scripts]# ./gs_push.sh
Usage: ./gs_push.sh <IP Address of Grandstream device> [P-value pairs] [password]

Here is an example of updating just the phone book URL using a more interactive mode to the script

[root@phone scripts]# ./gs_push.sh 10.0.0.172
Enter password [admin]: *****
Enter the P-code and value pair. Use '&' to separate multiple P-values.
Example: P330=3&P331=10.0.0.1/phonebook.xml&P332=30&P333=1
P-values:P331=10.0.0.4/phonebook.php
Config sent, rebooting...
Saving User Data...Done
Blocks Used ( 119 / 1024 )

Success!

Here are the commands I used to update all of the Grandstream devices communicating with my Asterisk PBX. I grep for the Grandstream OUI in the list of MAC addresses outputted from the “arp” command. These devices will usually be communicating with the Asterisk server periodically anyhow, and so probably they are already listed in the ARP table. To try and hedge that bet, all the devices are first sent a ping request to generate some new traffic. The output from the gs_push.sh script is suppressed by redirecting it to /dev/null, however in the event of a failure the script should announce via STDERR the IP address of the device it had trouble with.

Pvalues="P330=3&P331=10.0.0.4/phonebook.php&P332=30&P333=1"
asterisk -rx "sip show peers" |awk '{print $2}' |grep '\.' |xargs -n1 ping -c1 >/dev/null
arp |grep -i '00:0b:82' |awk '{print $1}' |xargs -i{} -n1 ./gs_push.sh "{}" "$Pvalues" admin >/dev/null

Download

#!/bin/bash

#  +----------------------------------------------------------------------+
#  | Grandstream GXP21xx IP Phone HTTP POSTing P-value config pusher      |
#  | May work with with other Grandstream devices, use at your own risk.  |
#  +----------------------------------------------------------------------+
#  | For the latest P-value descriptions consult the applicable firmware |
#  | release notes and configuration template descriptions:               |
#  | http://www.grandstream.com/tools/GAPSLITE/config-template.zip        |
#  +----------------------------------------------------------------------+
#  | The contents of this file are subject to the General Public License  |
#  | (GPL) Version 2 (the "License"); you may not use this file except in |
#  | compliance with the License. You may obtain a copy of the License at |
#  | http://www.opensource.org/licenses/gpl-license.php                   |
#  |                                                                      |
#  | Software distributed under the License is distributed on an "AS IS"  |
#  | basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See  |
#  | the License for the specific language governing rights and           |
#  | limitations under the License.                                       |
#  +----------------------------------------------------------------------+

get_password () {
        saveIFS="$IFS"
        IFS=$'\n'
        while read -s -n 1 char
        do
                case $(printf "%d\n" \'$char) in

                127)
                        if [ "${#passwd}" -gt 0 ]; then
                                echo -ne "\b \b"
                                passwd="${passwd:0:${#passwd}-1}"
                        fi
                        ;;
                0)
                        echo
                        break
                        ;;
                *)
                        echo -n "*"
                        passwd+=$char
                esac
        done
        IFS=$saveIFS
}

target_ip="$1"
if [ -z "$target_ip" ]; then
        echo "Usage: $0 <IP Address of Grandstream device> [P-value pairs] [password]" >&2
        exit 1
fi

Pvalues="$2"
passwd="$3"

if [ -z "$passwd" ]; then
        # If the second argument isn't a valid P-code pair use it as the password
        if [ "${Pvalues//'='}" == "$Pvalues" ]; then
                passwd="$Pvalues"
                unset Pvalues
        fi
fi
if [ -z "$passwd" ]; then
                echo -n "Enter password [admin]: "
                get_password
                if [ -z "$passwd" ]; then
                        passwd="admin"
                fi
fi

if [ -z "$Pvalues" ]; then
        # See if anything is waiting on stdin
        read -t1 Pvalues
        if [ -z "$PValues" ]; then
                # Or just ask the user..
                echo "Enter the P-code and value pair. Use '&' to separate multiple P-values."
                echo "Example: P330=3&P331=10.0.0.1/phonebook.xml&P332=30&P333=1"
                echo -n "P-values: "
                read Pvalues
        fi
fi

get_sid() {
        while read -r || [[ -n "$REPLY" ]]
        do
                if [ -n "$sid" ]; then
                        break
                fi
                if [ "${REPLY:0:23}" == "Set-Cookie: session_id="  ]; then
                        sid="${REPLY:23}"
                        sid="${sid//;}"
                fi
                if [ "${REPLY//'"sid"'}" != "$REPLY" ]; then
                        sid="${REPLY#*'"sid"'}"
                        sid="${sid#*'"'}"
                        sid="${sid%%'"'*}"
                fi

        done
        echo "$sid"
}


post_status() {
        # Old firmware says need to reboot, new firmware says success
        # new firmware doesn't end with a line break :(
        while read -r || [[ -n "$REPLY" ]]
        do
                if [ "${REPLY//eboot}" != "$REPLY" ]; then
                        echo "1"
                        break
                fi
                if [ "${REPLY//success}" != "$REPLY" ]; then
                        echo "1"
                        break
                fi
        done
}

reboot_status() {
        success=0
        headers=0
        while read -r || [[ -n "$REPLY" ]]
        do
                if [ -z "$REPLY" ]; then
                        # header data done
                        headers=1
                fi
                if [ "${REPLY//'{"results":['}" != "$REPLY" ]; then
                        if [ "${REPLY//[1]}" != "$REPLY" ]; then
                                success=1
                        fi
                        break
                fi
                if [ "${REPLY//'savereboot'}" != "$REPLY" ]; then
                        success=1
                        break
                fi
                if [ "${REPLY//'relogin'}" != "$REPLY" ]; then
                        success=1
                        break
                fi
                # fw 1.0.1.56 requires a different reboot action
                if [ "${REPLY//'The requested URL was not found'}" != "$REPLY" ]; then
                        curl -s -i -b "session_id=$sid" "http://$target_ip/cgi-bin/rs" |reboot_status
                        success=2
                fi

                # print extra saving info mixed in the headers, but ignore other bits
                if [ "${REPLY//:}" != "$REPLY" ]; then
                        unset REPLY
                fi
                if [ "${REPLY:0:4}" == "HTTP" ]; then
                        unset REPLY
                fi
                if [ "${REPLY:0:1}" == "<" ]; then
                        unset REPLY
                fi
                if [ "$headers" == "0" ] && [ -n "$REPLY" ]; then
                        echo "$REPLY"
                fi

        done
        if [ "$success" == "1" ]; then
                echo "Success!"
        fi
        if [ "$success" == "0" ]; then
                echo "Fail. ($target_ip)" >&2
        fi
}


if [ -z "$Pvalues" ]; then
        echo "No configuration parameters specified." >&2
        exit 1
fi

# Older firmware passed password as "P2", newer firmware passes as "password"
# <form> action is the same
sid="$(curl -s -i -d "P2=$passwd&password=$passwd" "http://$target_ip/cgi-bin/dologin" |get_sid)"

if [ -z "$sid" ]; then
        echo "Login failed." >&2
        exit 1
fi

# <form> action for config update is different across firmware versions
# Try old firmware style first
status="$(curl -s -i -b "session_id=$sid" -d "$Pvalues&update=Update" "http://$target_ip/cgi-bin/update" |post_status)"

if [ -z "$status" ]; then
        # Try new firmware config update action
        status="$(curl -s -i -b "session_id=$sid" -d "sid=$sid&$Pvalues" "http://$target_ip/cgi-bin/api.values.post" |post_status)"
fi

# Try new firmware way if the last attempt failed
if [ -n "$status" ]; then
        echo "Config sent, rebooting..."
        # Old firmware used query_string to pass REBOOT, new firmware uses POST data
        curl -s -i -d "request=REBOOT&sid=$sid" -b "session_id=$sid" "http://$target_ip/cgi-bin/api-sys_operation?REBOOT" |reboot_status
else
        echo "Couldn't update configuration ($target_ip)" >&2
fi
This entry was posted in General Nonsense, Telephony and tagged , , . Bookmark the permalink.

One Response to Grandstream Simple HTTP Configuration Pusher

  1. Gr says:

    Hi Chris,

    Thank you for the scripts.
    I am trying to build interface with cURL and PHP that allows me to make certain operations on the phone without using web-ui. For the P values everything I tested is working, but is difficult for me to figure out how to upload wallpaper. Setting P2916 to 3 and P2917 to the web url of the image works, but I want to do it with upload. Actually, I made attempt with cURL, as follows:
    For CURLOPT_POSTFIELDS I assigned “&sid=”.$sid.”&file=@img/test.png”
    Unfortunately, it doesn’t work.

    I will be glad if you give me any solution for this.
    Thak you in advance!

    BR,
    Gr

Leave a Reply

Your email address will not be published. Required fields are marked *