Tag: configuration update

  • Grandstream Simple HTTP Configuration Pusher

    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 [archive.org]. 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