Jul 202012
 

This is the script that I have on my GCM server. I am posting it here in the hopes that I can get some feedback on it, and perhaps also help some other people setting up GCM servers.

The PHP script for users to register themselves is here: http://skipstechtalk.net/2012/07/31/my-php-script-for-storing-gcm-user-registration/

The PHP script for to store device registration id’s is here: http://skipstechtalk.net/2012/07/31/my-php-script-for-gcm-device-registration/

<?php
/*
Third-party Application server for the PowerHome Connector for Android
C2DM and GCM applications. Takes an incoming HTTP GET request and
sends the correctly encoded message to the Google server, for ultimate
passing to the registered devices.
 
The requests should be sent as (all on one line, of course)
https://phc-a.net/send.php?username=myusername
	&password=mypass&var1=var1value&var2=var2value
 
additionally, requests may be sent as
https://phc-a.net/send.php?username=myusername
	&password=mypass&varname=myvar&varvalue=myvarvalue
but this format only supports a single variable/value pair.
 
The app looks at the returned message from the google server to determine if 
any changes need to be made, such as deleting registration id's
*/
 
$dbuser = 'mydbusername';
$dbname = 'phcadb';
$dbpasswd = 'mydbpassword';
$dbhost = 'my.dbhost.net';
 
// hold the data for the JSON object sent to the google server
$devices = array();
$data = array();
 
// php keeps track of the db connection. Each sql query here uses this 
// connection
$dbh = @mysql_connect($dbhost, $dbuser, $dbpasswd);
if (!$dbh) {
	echo "Could not connect to MySQL server on " . $dbhost;
	die();
}
 
if (!@mysql_select_db($dbname, $dbh)) {
	echo "Could not connect to database " . $dbname;
	die();
}
 
// used to determine whether to print debugging information
$verbose = $_REQUEST[verbose];
 
if ($verbose==true) {
	echo "<a href='help.html'>See this page for help</a><br />\n";
}
 
// for debugging, store each url in a database
//	$sql = "LOCK TABLE incomingsql WRITE";
//	query($sql);
//	$url = curPageURL();
//	$sql = "INSERT INTO incomingsql (sqlx) VALUES ('$url')";
//	$result = query($sql);
//	$sql = "UNLOCK TABLES";
//	query($sql);
 
if ($verbose==true) echo "The url was: $url<br />";
 
// check the password
$salt = GetSalt();
 
// get the stored password for the username passed in
$storedEncryptedPassword = GetStoredEncryptedPassword();
 
// hash the password that was passed in
$encryptedPassword=hash('sha512', $salt.$_REQUEST[password]);
for($i=0; $i<5000; $i++){
   $encryptedPassword=hash('sha512', $salt.$encryptedPassword);
}
 
// If the passwords match, then check for variable names and values 
// to be sent
if ($storedEncryptedPassword == $encryptedPassword) {
	if ($verbose==true) {
		echo "Username/Password match. Continue to send C2DM message.<br />";
	}
 
	// load up the $data array with the variable names and values
	foreach($_REQUEST as $key => $value) {
		switch ($key) {
			case "verbose":
			case "password":
			case "varvalue":
			case "username":
				break;
			case "varname":
				if ($verbose==true) {
					echo "Using varname/varvalue syntax<br />";
				}
				$varname = mysql_real_escape_string($value);
				$varvalue = $_REQUEST[varvalue];
 
				// prevent sql injection
				$varvalue = addslashes($varvalue);
				$varvalue = strip_tags($varvalue);
				if ($verbose==true) echo "Varname: " . $varname 
					. "; VarValue: " . $varvalue . "<br />";
				$data[$varname]=$varvalue;
				UpdateLastMessageSentTime($verbose);
				break;
			default:
				if ($verbose==true) {
					echo "Using direct variable syntax<br />";
				}
				$value = mysql_real_escape_string( $value );
 
				// prevent sql injection
				$value = addslashes($value);
				$value = strip_tags($value);
				if ($verbose==true) {
					echo "Varname: " . $key . "; VarValue: " . $value 
						. "<br />";
				}
				$data[$key]=$value;
				UpdateLastMessageSentTime($verbose);
				break;
		}
	}
}
else
{
	header('x', true, 401);
	die("Bad Username/Password");
}
 
if ($verbose==true) {
	echo "The data array is:<br />";
	print_r($data);
	echo "<br />";
}
 
// get all of the user's GCM devices
$sql = "SELECT * FROM userdevices WHERE username = "
           . "'$_REQUEST[username]' AND message_type='GCM'";
$result = query($sql);
if ($verbose==true) echo "Sending to the following GCM devices:<br />";
$devicecount = mysql_num_rows($result);
if ($devicecount==0) {
	if ($verbose==true) echo "--none--<br />";
}
while ($row = mysql_fetch_array($result)) {
	array_push($devices, $row[registration_id]);
	if ($verbose==true) echo $row[phone_name] . "<br />";
}
 
$devicecount = mysql_num_rows($result);
if ($devicecount>0) {
	if ($verbose==true) echo "There are $devicecount GCM devices<br />";
	SendGCM($verbose, $devices, $data);
}
 
// reset devices for C2DM
$devices = array();
 
// get all of the user's C2DM devices
$sql = "SELECT * FROM userdevices WHERE username = "
     . "'$_REQUEST[username]' AND message_type='C2DM'";
$result = query($sql);
$devicecount = mysql_num_rows($result);
if ($verbose==true) echo "There are $devicecount C2DM devices<br />";
if ($verbose==true) echo "Sending to the following C2DM devices:<br />";
if ($devicecount==0) {
	if ($verbose==true) echo "--none--<br />";
}
while ($row = mysql_fetch_array($result)) {
	array_push($devices, $row[registration_id]);
	if ($verbose==true) echo $row[phone_name] . "<br />";
}
 
foreach ($devices as $device) {
	SendC2DM($verbose, $device, $data);
}
 
// All done!
 
//
//
// FUNCTIONS go below here
//
//
function SendGCM ($verbose, $devices, $data) {
	if ($verbose==true) {
		echo "The data array is:<br />";
		print_r($data);
		echo "<br />";
		echo "The GCM devices array is:<br />";
		print_r($devices);
		echo "<br />";
	}
	$message = json_encode(array("registration_ids"=>$devices, 
		"data"=>$data));
	if ($verbose==true) echo "JSON message:<br />";
	if ($verbose==true) echo "$message" . "<br />";
	if ($verbose==true) echo "Sending over GCM<br />";
 
	// My GCM API key. KEEP SECRET!!!!!
	$phcaApiKey = "AIzaSAbCdEfGhIjKlnjkyXp-DCUMnOpQra8"; // SECRET!!!
 
	$url = "https://android.googleapis.com/gcm/send";
 
	$headers = array('Authorization: key=' . $phcaApiKey, 
		"Content-Type: application/json");
 
	if ($verbose==true) echo "Sending POST to GCM server. <br />";
 
	$x = curl_init($url); 
	curl_setopt($x, CURLOPT_HTTPHEADER, $headers);
	curl_setopt($x, CURLOPT_HEADER, 1); 
	curl_setopt($x, CURLOPT_POST, 1); 
	curl_setopt($x, CURLOPT_POSTFIELDS, $message); 
	curl_setopt($x, CURLOPT_RETURNTRANSFER, 1); 
	curl_setopt($x, CURLOPT_SSL_VERIFYPEER, false);
	$response = curl_exec($x); 
	$http_code = curl_getinfo($x, CURLINFO_HTTP_CODE);
 
	// request has been sent. Now get the response. The response 
	// is in the form of a JSON object, formatted as
	/*
		{ "multicast_id": 216,
		  "success": 3,
		  "failure": 3,
		  "canonical_ids": 1,
		  "results": [
		    { "message_id": "1:0408" },
		    { "error": "Unavailable" },
		    { "error": "InvalidRegistration" },
		    { "message_id": "1:1516" },
		    { "message_id": "1:2342", "registration_id": "32" },
		    { "error": "NotRegistered"}
		  ]
		}
	*/
 
	$header_size = curl_getinfo($x,CURLINFO_HEADER_SIZE);
	$body = substr( $response, $header_size );
 
	$json_response = json_decode($body);
	$results = $json_response->results;
 
	// first, print some debugging information
	if ($verbose==true) {
		echo "The response from the C2DM server was:<br />";
		print_r ($response);
		echo "<br />";
		$header = substr($response, 0, $header_size);
		echo "Header: $header<br />\n";
		echo "Body: $body<br />\n";
		echo "JSON decoded response:<br />";
		print_r($json_response);
		echo "<br />";
		echo "Results:<br />";
		print_r($results);
		echo "<br />";
	}
 
	$device_number = 0;
	foreach ($results as $device_response) {
		if (is_array($device_response)) {
			// only happens when a registration id needs to be updated.
			$old_id = $devices->$device_number;
			$new_id = $device_response->registration_id;
			if ($verbose==true) {
				echo "Updating the registration id from "
				   . "$old_id to $new_id.<br />\n";
			}
			$sql = "userdevices SET registration_id = "
			     . "'$new_id' WHERE registration_id='$old_id'";
			if ($verbose==true) echo $sql . "<br />";
			$result2 = query($sql);
		} else {
			if ($device_response->error == "NotRegistered") {
				// delete the device from the userdevices table
				if ($verbose==true) {
					echo "Deleting this entry from the devices table. "
					. "It has been unregistered.<br />\n";
				}
				$sql = "LOCK TABLE userdevices WRITE";
				query($sql);
				$sql = "DELETE FROM userdevices WHERE registration_id='"
 				   . $devices[$device_number] . "'";
				if ($verbose==true) echo $sql . "<br />";
				$result2 = query($sql);
				if ($verbose==true) echo "The result was: ";
				if ($verbose==true) print_r ($result2);
				$sql = "UNLOCK TABLES";
				query($sql);
			}
			if ($device_response->error == "InvalidRegistration") {
				// Not very likely. Possible corruption in the database???
				if ($verbose==true) {
					echo "ERROR!! Invalid Registration should never "
					. "happen. Please inform the developer and perhaps "
					. "try unregistering and re-registering your device.";
				}
			}
			if ($device_response->error == "Unavailable") {
				// GCM Service is temporarily down. Try again later
				if ($verbose==true) {
					echo "Google GCM dervice is temporarily down. Try "
					. "sending again later.";
				}
			}
		}
		$device_number++;
	}
 
	// close the curl object
	curl_close($x); 
 
	if ($verbose==true) {
		echo "You should not see any errors in the response. If there "
		. "are no errors, you should see an updated variable in the "
		. "status text on your phone.<br />";
	}
}
 
function SendC2DM($verbose, $device, $data) {
 
	if ($verbose==true) echo "Sending over C2DM<br />";
	/*
	Ultimately, this is how the request should be formatted:
	curl --header "Authorization: GoogleLogin auth=your_authenticationid" \
	"https://android.apis.google.com/c2dm/send" \
	-d registration_id=your_registration \
	-d "data.payload=This data will be send to your application as payload" \
	-d collapse_key=0
	*/
	$googleAuth = "A-VERY-LONG-STRING-WITH-A-BUNCH-OF-LETTERS-AND-NUMBERS";
	/*
	$google Auth can be obtained using curl like this:
	(all on one line, begin below this line)
	C:\>curl https://www.google.com/accounts/ClientLogin 
		-d Email=skip.morrow.mobile@gmail.com 
		-d "Passwd=kklirctpnucvnelf" 
		-d accountType=GOOGLE 
		-d source=Google-cURL-Example -d service=ac2dm -k
	(end of command)
 
	Note that the password is a one-time use, application specific password.
 
	*/
 
	$url = "https://android.apis.google.com/c2dm/send";
	//$regid = GetRegId();
	$data_msg .= urlencode($key) ."=". urlencode($value); 
	$size=strlen($data_msg);
 
	$data_msg = "";
 
	if ($verbose==true) echo "<br />Registration_id = " 
		. $device . "<br />";
 
	// add "data." to all of the keys in the $data array
 
	foreach($data as $key => $value) {
		$data["data." . $key] = $value;
		unset($data[$key]);
	}
	// $data has all of the variables. Just add the registration_id 
	// and collapse_key and it will be ready to send
	$data["registration_id"]=$device;
	$collapse_key = date("Hi");
 
	$data["collapse_key"]=$collapse_key;
 
	if ($verbose==true) print_r ($data);
	if ($verbose==true) echo "<br />";
	//echo "Headers: ";
	$headers = array('Authorization: GoogleLogin auth=' . $googleAuth);
	//print_r($headers);
	if ($verbose==true) echo "Sending POST to C2DM server. <br />";
 
	$x = curl_init("https://android.apis.google.com/c2dm/send"); 
	if ($headers) curl_setopt($x, CURLOPT_HTTPHEADER, $headers);
	curl_setopt($x, CURLOPT_HEADER, 1); 
	curl_setopt($x, CURLOPT_POST, 1); 
	curl_setopt($x, CURLOPT_POSTFIELDS, $data); 
	curl_setopt($x, CURLOPT_RETURNTRANSFER, 1); 
	curl_setopt($x, CURLOPT_SSL_VERIFYPEER, false);
	$data = curl_exec($x); 
	curl_close($x); 
	$response = $data;
	if ($verbose==true) {
		echo "The response from the C2DM server was:<br />";
		print_r ($data);
		echo "<br />";
		echo "<br />";
		echo "<br />\n";
		echo "You should not see any errors in the response. "
			. "If there are no errors, you should see an updated "
			. "variable in the status text on your phone.<br />";
	}
}
 
function curPageURL() {
 $pageURL = 'http';
 if ($_SERVER["HTTPS"] == "on") {$pageURL .= "s";}
 $pageURL .= "://";
 if ($_SERVER["SERVER_PORT"] != "80") {
  $pageURL .= $_SERVER["SERVER_NAME"].":".$_SERVER["SERVER_PORT"]
	.$_SERVER["REQUEST_URI"];
 } else {
  $pageURL .= $_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
 }
 return $pageURL;
}
 
function GetRegId() {
	$sql = "SELECT * FROM user WHERE username = '$_REQUEST[username]'";
	$result = query($sql);
	if (mysql_num_rows($result) == 0) // bad username
	{
		header('x', true, 401);
		die("Bad Username");
	}
	while ($row = mysql_fetch_assoc($result)) {
	    return $row["regId"];
	}
}
 
 
function GetSalt() {
	$sql = "SELECT * FROM user WHERE username = '$_REQUEST[username]'";
	$result = query($sql);
	if (mysql_num_rows($result) == 0) // bad username
	{
		header('x', true, 401);
		die("Bad Username");
	}
	while ($row = mysql_fetch_assoc($result)) {
	    return $row["salt"];
	}
}
 
function GetStoredEncryptedPassword() {
	$sql = "SELECT * FROM user WHERE username = '$_REQUEST[username]'";
	$result = query($sql);
	if (mysql_num_rows($result) == 0) // bad username, should not happen
	{
		header('x', true, 401);
		die("Bad Username");
	}
	while ($row = mysql_fetch_assoc($result)) {
	    return $row["passwordHash"];
	}
}
 
function UpdateLastMessageSentTime() {
	$sql = "LOCK TABLE user WRITE";
	query($sql);
	$sql = "UPDATE user SET last_msg_timestamp=now() WHERE username = "
	. "'$_REQUEST[username]'";
	$result = query($sql);
	$sql = "UNLOCK TABLES";
	query($sql);
}
 
function query($query) {
if(!($result = mysql_query($query)))
	{
		//can't execute query
		if ($verbose==true) echo ( "Couldn't query table!<br>\n");
		if ($verbose==true) {
			echo ( "MySQL Reports: " . mysql_error() . "<br>\n");
		}
		exit();
	}
return $result;
}
?>

Here’s the SQL scripts to create the database tables that I am using:

 
-- phpMyAdmin SQL Dump
-- version 3.3.10.4
-- http://www.phpmyadmin.net
--
-- Host: my.dbhost.net
-- Generation Time: Jul 20, 2012 at 11:07 AM
-- Server version: 5.1.39
-- PHP Version: 5.2.17
 
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
 
--
-- Database: `phcadb`
--
 
-- --------------------------------------------------------
 
--
-- Table structure for table `incomingsql`
--
 
CREATE TABLE IF NOT EXISTS `incomingsql` (
  `index` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `sqlx` VARCHAR(1024) NOT NULL,
  PRIMARY KEY (`index`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 
-- --------------------------------------------------------
 
--
-- Table structure for table `user`
--
 
CREATE TABLE IF NOT EXISTS `user` (
  `username` VARCHAR(64) NOT NULL,
  `passwordHash` VARCHAR(1024) NOT NULL,
  `salt` VARCHAR(26) NOT NULL,
  `registration_date` DATE NOT NULL,
  `email` VARCHAR(60) DEFAULT NULL,
  `last_msg_timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`username`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 
-- --------------------------------------------------------
 
--
-- Table structure for table `userdevices`
--
 
CREATE TABLE IF NOT EXISTS `userdevices` (
  `registration_id` VARCHAR(256) NOT NULL,
  `username` VARCHAR(64) NOT NULL,
  `device_id` VARCHAR(128) NOT NULL,
  `is_sending` tinyint(1) NOT NULL,
  `message_type` enum('C2DM','GCM') DEFAULT NULL COMMENT 'Either C2DM or GCM',
  `phone_name` VARCHAR(64) DEFAULT NULL,
  PRIMARY KEY (`registration_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 Posted by at 5:54 pm

  7 Responses to “My Google Cloud Messaging (GCM) PHP script”

  1. Hye… Nice code… btw may i know how u handle registration process so that the reg id can be store in DB?

  2. Hello, I’m very new to GCM and was wondering if you had an example of what would be talking to this php server script? I understand that this script is talking to GCM Google but what’s talking to this script?
    Thanks,
    SS

  3. The comments at the top provide examples of how GCM messages are posted. The only other communications with the server are for registering new devices and registering new users. Anything that can make an HTTP POST/GET request can post messages using this script, including a web browser or desktop app or android app. All it has to do is format the request as documented in the code. @mdynani, I will post the code for the registration in a new post.

  4. [...] this saved as my index.php file so it is served up whenever someone navigates to my server. See http://skipstechtalk.net/2012/07/20/my-google-cloud-messaging-gcm-php-script/ for the script for actually sending GCM messages. There will also be one more script for saving [...]

  5. where do i put my project id?

  6. You don’t need the project ID for sending GCM messages. The API key “contains” the project ID. The API key is all you need.

Leave a Reply