Tag: Grandstream

  • 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
  • Grandstream Phone Book Creator for Elastix

    Grandstream Phone Book Creator for Elastix

    Do you run the Elastix PBX management environment to Asterisk? Do you use Grandstream IP Phones? If you answered yes to both of these questions, then this might be helpful to you.

    Grandstream GXP21xx/GXP14xx/GXP116x IP phones (and possibly others) can be configured to automatically download an XML formatted phone book file. The Elastix interface already has a place for naming the users, wouldn’t it be great to use that data for making an always up-to-date phone book file? That is the idea with this script. It was written for use with Elastix version 2.0, but will probably work with others (in the 2.x branch), and maybe FreePBX as well, I haven’t really tested.

    To “install” the script simply put it in the root of your Elastix web directory space.

    The format of the XML output is something like this:

    <?xml version="1.0" encoding="UTF-8"?>
    <AddressBook>
            <Contact>
                    </Lastname>Test User<LastName/>
                    <FirstName></FirstName>
                    <Phone>
                            <phonenumber>1100</phonenumber>
                            <accountindex>1</accountindex>
                            <downloaded>0</downloaded>
                    </Phone>
            </Contact>
    </AddressBook>

    Two versions of the script are presented, you only need one. The first script uses functions included inside of various files that should be present (AFAIK) in your Elastix web root. It is slower and will undoubtedly use more resources during execution. It may also be more flexible and resilient in that it will always use the latest version of the functions available to it. This is a double-edged sword of course, and might end up breaking it as well. Enter the second script, which uses just the applicable parts of the code from those aforementioned functions without needing to include any files. This script will probably work provided the database table stays the same, but your mileage may vary.

    You can download both scripts from here: Download

    The shorter version:

    <?php
    /* vim: set expandtab tabstop=4 softtabstop=4 shiftwidth=4:
      +———————————————————————-+
      | Grandstream GXP21xx/GXP14xx/GXP116x IP Phone XML Phone Book Creator  |
      | For Elastix version 2.0 (maybe others) http://www.elastix.org        |
      +———————————————————————-+
      | Made with recycled code, applicable rights                           |
      | Palosanto Solutions S. A., Coalescent Systems Inc, FreePBX, et al.   |
      +———————————————————————-+
      | 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.                                       |
      +———————————————————————-+
    */

    header("Content-Type: text/xml");

    require_once(‘admin/functions.inc.php’);
    require_once(‘admin/modules/core/functions.inc.php’);
    require_once(‘admin/common/php-asmanager.php’);
    require_once(‘DB.php’); //PEAR must be installed (why?)

    $db_user = $amp_conf["AMPDBUSER"];
    $db_pass = $amp_conf["AMPDBPASS"];
    $db_host = $amp_conf["AMPDBHOST"];
    $db_name = $amp_conf["AMPDBNAME"];

    $datasource = ‘mysql://’.$db_user.‘:’.$db_pass.‘@’.$db_host.‘/’.$db_name;
    $db = DB::connect($datasource);

    $extensions = core_users_list();
    echo "<?xml version="1.0" encoding="UTF8"?>\n";
    echo "<AddressBook>\n";
    $index = 0;
    if (isset($extensions)) {
        foreach ($extensions as $key=>$extension) {
            $index= $index + 1;
            echo "\n\n\t<Contact>";
            echo "\n\t\t<LastName>" . $extension[1] . "</LastName>";
            echo "\n\t\t<FirstName></FirstName>";
            echo "\n\t\t<Phone>";
            echo "\n\t\t\t<phonenumber>" . $extension[0] . "</phonenumber>";
            echo "\n\t\t\t<accountindex>" . $index . "</accountindex>";
            echo "\n\t\t</Phone>";
            echo "\n\t</Contact>\n";
            }
    }
    echo "</AddressBook>\n";
    ?>

    The more monolithic version:

    <?php
    /* vim: set expandtab tabstop=4 softtabstop=4 shiftwidth=4:
      +———————————————————————-+
      | Grandstream GXP21xx/GXP14xx/GXP116x IP Phone XML Phone Book Creator  |
      | For Elastix version 2.0 (maybe others) http://www.elastix.org        |
      +———————————————————————-+
      | Made from 99% recycled code, applicable rights                       |
      | Palosanto Solutions S. A., Coalescent Systems Inc, FreePBX, et al.   |
      +———————————————————————-+
      | 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.                                       |
      +———————————————————————-+
    */

    header("Content-Type: text/xml");

    define("AMP_CONF", "/etc/amportal.conf");

    $file = file(AMP_CONF);
    if (is_array($file)) {
        foreach ($file as $line) {
            if (preg_match("/^\s*([a-zA-Z0-9_]+)=([a-zA-Z0-9 .&-@=_!<>"\‘]+)\s*$/",$line,$matches)) {
                $amp_conf[ $matches[1] ] = $matches[2];
            }
        }
    }

    require_once(‘
    DB.php‘); //PEAR must be installed
    $db_user = $amp_conf["AMPDBUSER"];
    $db_pass = $amp_conf["AMPDBPASS"];
    $db_host = $amp_conf["AMPDBHOST"];
    $db_name = $amp_conf["AMPDBNAME"];

    $datasource = ‘
    mysql://’.$db_user.’:’.$db_pass.’@’.$db_host.’/’.$db_name;
    $db = DB::connect($datasource); // attempt connection

    $type="getAll";
    $results = $db->$type("SELECT extension,name,voicemail FROM users ORDER BY extension", null);
    foreach($results as $result){
        $extensions[] = array($result[0],$result[1],$result[2]);
    }

    #$extensions = core_users_list();
    echo "<?xml version="1.0" encoding="UTF8"?>\n";
    echo "<AddressBook>\n";
    $index = 0;
    if (isset($extensions)) {
        foreach ($extensions as $key=>$extension) {
            $index= $index + 1;
            echo "\n\n\t<Contact>";
            echo "\n\t\t<LastName>" . $extension[1] . "</LastName>";
            echo "\n\t\t<FirstName></FirstName>";
            echo "\n\t\t<Phone>";
            echo "\n\t\t\t<phonenumber>" . $extension[0] . "</phonenumber>";
            echo "\n\t\t\t<accountindex>" . $index . "</accountindex>";
            echo "\n\t\t</Phone>";
            echo "\n\t</Contact>\n";
        }
    }
    echo "</AddressBook>\n";
    ?>

    Before you can use the new phone book you have to update the phone to make it aware of it. I wrote a script that I talk about in another article for this purpose. If you only need to update one phone, or would rather do it manually the basic steps are outlined below.

    These screens differ with firmware version, but the general concept should remain roughly the same, for example on newer firmware versions for my phone the phone book options are under Phonebook and then Phonebook Management

    To make use of the phone book with the phone, navigate to the web interface on the device and login. I’m using a Grandstream GXP2120 phone. The default password is “admin”.
    grandstream_web_login

    After logging in click on the Settings tab and then choose Advanced Settings
    grandstream_web_settings_advanced

    Scroll down to the field Phonebook XML Download My Elastix installation redirects HTTP request to HTTPS. The Grandstream phone doesn’t seem to care if the certificate is backed by a common certificate authority. Most likely you’ll want to select HTTPS as shown. For Server Path leave off the protocol specification of the URL (e.g. http://, https://). Make sure to set a valid Download Interval (something of at least 5 or higher). Optionally chose to remove old entries in the phone book, this might help to avoid confusing stale data.
    grandstream_phonebook_settings

    Once the changes have been made you’ll need to reboot.
    grandstream_reboot