Skip to content

Instantly share code, notes, and snippets.

@wcoastsands
Last active July 28, 2023 22:54
Show Gist options
  • Save wcoastsands/cf6371d2a6aaaa04e4e2 to your computer and use it in GitHub Desktop.
Save wcoastsands/cf6371d2a6aaaa04e4e2 to your computer and use it in GitHub Desktop.
Unity Ads S2S Redeem Callback Example
<?php # callback.php (PHP 5) - Evaluates the Unity Ads S2S Redeem Callback.
//-----------------------------------------------------------------------------
// Each callback should contain the following parameters in the query string:
// pid -- product/game ID specified as part of the base callback URL
// sid -- user ID set through the Unity Ads SDK
// oid -- offer ID unique to each callback
// hmac -- HDMAC-MD5 hash of the query string
//-----------------------------------------------------------------------------
$public_ips_file = 'public_ips.json';
$mysql_connect_file = 'mysql_connect.php';
$header_200 = $_SERVER['SERVER_PROTOCOL'].' 200 OK';
$header_400 = $_SERVER['SERVER_PROTOCOL'].' 400 Bad Request';
$header_403 = $_SERVER['SERVER_PROTOCOL'].' 403 Forbidden';
$header_500 = $_SERVER['SERVER_PROTOCOL'].' 500 Internal Server Error';
$header = $header_500;
// Replace examples with valid email addresses.
$email = 'alerts@mydomain.com';
$reply = 'noreply@mydomain.com';
$subject = 'S2S Redeem Callback Alert';
if (isset($_GET['pid'])) $subject .= ' for '.$_GET['pid'];
$mailheaders = 'From: '.$reply."\r\n".
'Reply-To: '.$reply."\r\n".
'X-Mailer: PHP/'.phpversion();
$message = '';
function cidr_match ($addr, $prefix)
{
list ($subnet, $bits) = explode('/', $prefix);
$addr = ip2long($addr);
$subnet = ip2long($subnet);
$mask = -1 << (32 - $bits);
$subnet &= $mask;
return ($addr & $mask) == $subnet;
}
function is_valid_addr ($addr)
{
global $public_ips_file;
$public_ips = json_decode(file_get_contents($public_ips_file));
foreach ($public_ips->prefixes as $prefix)
{
if (cidr_match($addr,$prefix->ip_prefix)) return true;
}
return false;
}
function generate_hash ($params, $secret)
{
ksort($params);
$s = '';
foreach ($params as $key => $value)
{
$s .= "$key=$value,";
}
$s = substr($s, 0, -1);
return hash_hmac('md5', $s, $secret);;
}
function get_secret ($pid)
{
$secret = '';
$query = "SELECT secret FROM games WHERE pid = '$pid'";
$result = @mysql_query($query);
if ($result != false)
{
$row = mysql_fetch_assoc($result);
$secret = $row['secret'];
}
return $secret;
}
function is_valid_pid ($pid)
{
$query = "SELECT pid FROM games WHERE pid = '$pid'";
$result = @mysql_query($query);
return ($result != false && mysql_num_rows($result) > 0);
}
function is_unique_oid ($oid)
{
$query = "SELECT oid FROM callbacks WHERE oid = $oid";
$result = @mysql_query($query);
return ($result == false || mysql_num_rows($result) == 0);
}
function save_callback ($params, $hash)
{
$query = "INSERT INTO callbacks (oid, sid, pid, hmac, datetime) ".
"VALUES ('".$params['oid']."', '".$params['sid']."', '".
$params['pid']."', '".$hash."', UTC_TIMESTAMP())";
return @mysql_query($query);
}
function get_request_params_message ($params)
{
$msg = "Request Parameters:";
foreach ($params as $key => $value)
{
$msg .= "\r\n $key = $value";
}
return $msg;
}
function get_server_info_message ()
{
return "Server Info:".
"\r\n Remote IP: ".$_SERVER['REMOTE_ADDR'].
"\r\n User Agent: ".$_SERVER['HTTP_USER_AGENT'].
"\r\n Query String: ".$_SERVER['QUERY_STRING'];
}
if (count($_GET) == 0) // Accept 0 parameters as a test.
{
if (!file_exists($mysql_connect_file))
{
$header = $header_500;
$file_name = basename($mysql_connect_file);
$message = "Response: 500 - File not found ({$file_name}).";
}
else if (!file_exists($public_ips_file))
{
$header = $header_500;
$file_name = basename($public_ips_file);
$message = "Response: 500 - File not found ({$file_name}).";
}
else
{
$header = $header_200;
$message = "Response: 200 - Test OK.";
}
}
else if (empty($_GET['sid'])) // Validate sid parameter.
{
$header = $header_400;
$message = "Response: 400 - 'sid' value is not set.";
}
else if (empty($_GET['pid'])) // Verify pid parameter exists and is set.
{
$header = $header_400;
$message = "Response: 400 - 'pid' " .
((array_key_exists('pid',$_GET)) ?
"value is not set." : "parameter is missing.");
}
else if (!file_exists($public_ips_file)) // Verify file exists.
{
$header = $header_500;
$file_name = basename($public_ips_file);
$message = "Response: 500 - File not found ({$file_name}).";
}
else if (!is_valid_addr($_SERVER['REMOTE_ADDR'])) // Validate remote IP.
{
$header = $header_403;
$message = "Response: 403 - Request orignated from an invalid address.";
}
else
{
require_once($mysql_connect_file);
if ($dbc == false) // Verify DB connection.
{
$header = $header_500;
$message = "Response: 500 - Failed to connect to DB.";
}
else if (!is_valid_pid($_GET['pid'])) // Validate pid parameter value.
{
$header = $header_400;
$message = "Response: 400 - 'pid' value is invalid.";
}
else
{
$hash = $_GET['hmac'];
unset($_GET['hmac']);
$secret = get_secret($_GET['pid']);
if ($hash != generate_hash($_GET,$secret)) // Validate signature.
{
$header = $header_400;
$message = "Response: 400 - Signatures did not match.";
}
else if (!is_unique_oid($_GET['oid'])) // Validate oid parameter.
{
$header = $header_400;
$message = "Response: 400 - 'oid' value is not unique.";
}
else if (!save_callback($_GET,$hash)) // Attempt to save.
{
$header = $header_500;
$message = "Response: 500 - Failed to save callback data.";
}
else // Everything OK.
{
$header = $header_200;
$message = "Response: 200 - OK";
}
}
mysql_close();
unset($dbc);
}
$message .= "\r\n";
header($header);
print($message);
if ($header != $header_200) // Send email alert with details.
{
if (empty($email) || $email == 'alerts@mydomain.com')
{
print("Failed to send email alert! Address is invalid.");
}
else
{
$message .= "\r\n".get_request_params_message($_GET)."\r\n";
$message .= "\r\n".get_server_info_message()."\r\n";
mail($email,$subject,$message,$mailheaders);
}
}
exit();
?>
<?php # mysql_connect.php (PHP 5) - Connects to a MySQL database.
//-----------------------------------------------------------------------------
// For security reasons, the permissions on this file should be set to
// read-only for the owner, with no permissions for the group or others.
//
// Example:
// $ chmod 400 mysql_connect.php
//
// This file should also be placed outside of the web root directory.
//-----------------------------------------------------------------------------
define ('DB_HOST',''); // Set the hostname of the MySQL server.
define ('DB_NAME',''); // Set the database name.
define ('DB_USER',''); // Set the username.
define ('DB_PASS',''); // Set the password.
$dbc = @mysql_connect(DB_HOST,DB_USER,DB_PASS);
@mysql_select_db(DB_NAME);
?>
mysql> SHOW COLUMNS FROM callbacks;
+----------+-------------+------+-----+---------------------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------------------+-------+
| oid | varchar(16) | NO | PRI | NULL | |
| sid | varchar(24) | NO | | NULL | |
| pid | varchar(8) | NO | | NULL | |
| hmac | char(32) | NO | | NULL | |
| datetime | datetime | NO | | 0000-00-00 00:00:00 | |
+----------+-------------+------+-----+---------------------+-------+
5 rows in set (0.03 sec)
mysql> SHOW COLUMNS FROM games;
+----------+-----------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+-----------------------+------+-----+---------+-------+
| pid | varchar(8) | NO | PRI | NULL | |
| name | varchar(32) | NO | | NULL | |
| platform | enum('ios','android') | NO | | NULL | |
| secret | varchar(32) | NO | | NULL | |
+----------+-----------------------+------+-----+---------+-------+
4 rows in set (0.02 sec)
<?php # update_public_ips.php (PHP 5) - Creates a local copy of public_ips.json
//-----------------------------------------------------------------------------
// For efficiency and reliability reasons, a local copy of public_ips.json
// is used to validate the origin of Unity Ads S2S Redeem Callbacks.
//
// A cron job should be configured to run this script once every 24 hrs.
//
// Example:
// 50 0 * * * /usr/local/php5/bin/php "$HOME/html/update_public_ips.php"
//-----------------------------------------------------------------------------
$remote_file = 'http://static.applifier.com/public_ips.json';
$local_file = dirname(__FILE__).'/public_ips.json';
$data = file_get_contents($remote_file);
try { file_put_contents($local_file,$data); }
catch (Exception $e)
{
exit('Caught exception: '.$e->getMessage()."\r\n");
}
exit();
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment