Created
June 9, 2010 11:33
-
-
Save jystewart/431351 to your computer and use it in GitHub Desktop.
A slightly saner interface for JangoMail Sync than the one they supply
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* A slightly saner interface for JangoMail Sync than the one they supply | |
* | |
* This is a demonstration of how to produce a more security-aware interface for JangoMail's | |
* web database synchronisation system. It is not guaranteed in any way and is not meant | |
* to be seen as an example of best practice on any level. It does, however, demonstrate how | |
* the flaws in that system can be worked around to build a reasonable JSON-based interface | |
* when working with recent versions of PHP. | |
* | |
* See http://jystewart.net/process/2010/06/jangomail-lackadaisical-security-and-a-workaround/ | |
* for more. | |
* | |
* @author James Stewart <[email protected]> | |
* @date 9th June 2010 | |
*/ | |
/** | |
* Very crude way to handle errors | |
*/ | |
function return_error($code_and_message) { | |
header('HTTP/1.1 ' . $code_and_message); | |
echo $code_and_message; | |
exit; | |
} | |
/** | |
* Do our basic mysql connection. In a function to make sure we only call it when we need it. | |
*/ | |
function make_mysql_connection() { | |
mysql_connect(DB_HOST, DB_USER, DB_PASSWORD); | |
} | |
/** | |
* Make sure this is a real email address and give a 406 if not | |
*/ | |
function check_email($address) { | |
$email = filter_var($address, FILTER_VALIDATE_EMAIL); | |
if (! $email) { | |
return_error('406 Unacceptable'); | |
} | |
return $email; | |
} | |
/** | |
* Handle the 'massmail' request, returning a list of active email addresses | |
* | |
* @return String | |
*/ | |
function retrieve_addresses() { | |
$fields = array('name', 'email'); | |
$output = implode(',', $fields) . FIELDNAME_DELIMITER; | |
make_mysql_connection(); | |
$query = "SELECT `name`, `email` FROM `email_subscribers` | |
WHERE `unsubscribed` = 0 AND bouncing = 0"; | |
$rs = mysql_query($query); | |
while ($arr_row = mysql_fetch_row($rs)) { | |
$output .= implode(COL_DELIMITER, $arr_row) . ROW_DELIMITER; | |
} | |
return $output; | |
} | |
/** | |
* Handle the 'unsubscribe' action - mark our record for the address as unsubscribed | |
* | |
* @param stdClass | |
* @return boolean | resource | |
*/ | |
function handle_unsubscribe($params) { | |
$email = check_email($params->email); | |
make_mysql_connection(); | |
$query = sprintf("UPDATE `email_subscribers` SET `unsubscribed` = 1 WHERE `email` = '%s'", | |
mysql_real_escape_string($email)); | |
return mysql_query($query); | |
} | |
/** | |
* Handle the 'bounce' action - mark our record for the address as bouncing | |
* | |
* @param stdClass | |
* @return boolean | resource | |
*/ | |
function handle_bounce($params) { | |
$email = check_email($params->email); | |
make_mysql_connection(); | |
$query = sprintf("UPDATE `email_subscribers` SET `bouncing` = 1 WHERE `email` = '%s'", | |
mysql_real_escape_string($email)); | |
return mysql_query($query); | |
} | |
/** | |
* Handle the 'change' action - updating our email address for the user | |
* | |
* @param stdClass | |
* @return boolean | resource | |
*/ | |
function handle_change($params) { | |
$new_email = check_email($params->new_email); | |
$old_email = check_email($params->old_email); | |
make_mysql_connection(); | |
$query = sprintf("UPDATE `email_subscribers` SET `email` = '%s' WHERE `email` = '%s'", | |
mysql_real_escape_string($new_email), mysql_real_escape_string($old_email)); | |
return mysql_query($query); | |
} | |
/* Rather crude way to check IP address is in range 209.173.141.193 - 209.173.141.255 */ | |
preg_match('/(.+?)\.(\d+)$/', $_SERVER['REMOTE_ADDR'], $ip_parts); | |
if ($ip_parts[1] != '209.173.141' || $ip_parts[2] < 193 || $ip_parts[2] > 255) { | |
return_error('403 Forbidden'); | |
} | |
set_time_limit(1200); | |
define('USERNAME', 'sample'); | |
define('PASSWORD', 'sample'); | |
define('DB_HOST', 'localhost'); | |
define('DB_USER', 'user'); | |
define('DB_PASSWORD', 'password'); | |
define('ROW_DELIMITER', 'WG0ROWWG0'); | |
define('COL_DELIMITER', 'WG0COLWG0'); | |
define('FIELDNAME_DELIMITER', '___ASDF---BREAK'); | |
define('JANGO_EOF', 'WANGO-ENDOFDATASTREAM'); | |
/* Process input */ | |
$temp_params = $_POST; | |
function clean_params($param) { | |
return str_replace('ABC-WANGOMAIL-ABC', chr(0), $param); | |
} | |
array_walk($temp_params, 'clean_params'); | |
$params = array(); | |
$strFieldNameDelimiter = '___ASDF---BREAK'; | |
$params['action'] = $temp_params['action']; | |
$params['username'] = $temp_params['username']; | |
$params['password'] = $temp_params['password']; | |
$params['data'] = json_decode($temp_params['querystring']); | |
/* Authenticate */ | |
if ($params['username'] != USERNAME || $params['password'] != PASSWORD) { | |
return_error('403 Forbidden'); | |
} | |
/* Dispatch actions */ | |
switch ($params['action']) { | |
# massmail actually means 'get list of users for mass mailing' | |
case 'massmail': | |
echo retrieve_addresses(); | |
break; | |
case 'unsubscribe': | |
if (handle_unsubscribe($params['data'])) { | |
echo 'unsubscribe-sync-success'; | |
} else { | |
echo 'unsubscribe-sync-failure'; | |
} | |
break; | |
case 'bounce': | |
if (handle_bounce($params['data'])) { | |
echo 'bounce-sync-success'; | |
} else { | |
echo 'bounce-sync-failure'; | |
} | |
break; | |
case 'change': | |
if (handle_change($params['data'])) { | |
echo 'change-sync-success'; | |
} else { | |
echo 'change-sync-failure'; | |
} | |
break; | |
case 'view': | |
case 'click': | |
case 'sent': | |
case 'job': | |
case 'action': | |
case 'forward': | |
case 'test': | |
# We don't care about any of these in our system so pretend they worked | |
echo $action . 'sync-success'; | |
break; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment