/**
* smd_access_keys
*
* A Textpattern CMS plugin for secure tokenized access to resources:
* -> Time-based or access attempt limits
* -> Untamperable URL-based keys
* -> Optional IP logging
*
* @author Stef Dawson
* @link http://stefdawson.com/
*/
// TODO: Add shortcut to send a newly-generated admin-side key to a user? (dropdown of users + e-mail template in prefs?)
// Add auto-deletion of expired keys pref:
// -> File download keys can be deleted on any key access (expiry window is known via prefs)
// -> Other key accesses can only be deleted when that key is used
// -> Configurable grace period after expiry, before deletion
// Obfusctaed URLs (see http://radioartnet.net/11/2011/07/05/samuel-beckett-words-and-music/)
// Query an access key and separate it into its component parts for convenient testing
if(@txpinterface == 'admin') {
global $smd_akey_event, $smd_akey_styles;
$smd_akey_event = 'smd_akey';
$smd_akey_styles = array(
'list' =>
'.smd_hidden { display:none; }',
);
add_privs($smd_akey_event, '1');
add_privs('plugin_prefs.smd_access_keys', '1');
register_tab('extensions', $smd_akey_event, smd_akey_gTxt('smd_akey_tab_name'));
register_callback('smd_akey_dispatcher', $smd_akey_event);
register_callback('smd_akey_welcome', 'plugin_lifecycle.smd_access_keys');
register_callback('smd_akey_prefs', 'plugin_prefs.smd_access_keys');
}
global $smd_akey_prefs;
$smd_akey_prefs = array(
'smd_akey_file_download_expires' => array(
'html' => 'text_input',
'type' => PREF_HIDDEN,
'position' => 10,
'default' => '3600',
),
'smd_akey_salt_length' => array(
'html' => 'text_input',
'type' => PREF_HIDDEN,
'position' => 20,
'default' => '8',
),
'smd_akey_log_ip' => array(
'html' => 'yesnoradio',
'type' => PREF_HIDDEN,
'position' => 30,
'default' => '0',
),
);
if (!defined('SMD_AKEYS')) define("SMD_AKEYS", 'smd_akeys');
register_callback('smd_access_protect_download', 'file_download');
// ********************
// ADMIN SIDE INTERFACE
// ********************
// Jump off point for event/steps
function smd_akey_dispatcher($evt, $stp) {
if(!$stp or !in_array($stp, array(
'smd_akey_table_install',
'smd_akey_table_remove',
'smd_akey_create',
'smd_akey_prefs',
'smd_akey_prefsave',
'smd_akey_multi_edit',
'smd_akey_change_pageby',
))) {
smd_akey('');
} else $stp();
}
// Bootstrap when installed/deleted
function smd_akey_welcome($evt, $stp) {
$msg = '';
switch ($stp) {
case 'installed':
smd_akey_table_install(0);
$msg = 'Restrict your Txp world :-)';
break;
case 'deleted':
smd_akey_table_remove(0);
break;
}
return $msg;
}
// Main admin interface
function smd_akey($msg='') {
global $smd_akey_event, $smd_akey_list_pageby, $smd_akey_styles, $logging, $smd_akey_prefs;
pagetop(smd_akey_gTxt('smd_akey_tab_name'), $msg);
if (smd_akey_table_exist(1)) {
extract(gpsa(array('page', 'sort', 'dir', 'crit', 'search_method')));
if ($sort === '') $sort = get_pref('smd_akey_sort_column', 'time');
if ($dir === '') $dir = get_pref('smd_akey_sort_dir', 'desc');
$dir = ($dir == 'asc') ? 'asc' : 'desc';
switch ($sort) {
case 'page':
$sort_sql = 'page '.$dir.', time desc';
break;
case 'triggah':
$sort_sql = 'triggah '.$dir.', time desc';
break;
case 'maximum':
$sort_sql = 'maximum '.$dir.', time desc';
break;
case 'accesses':
$sort_sql = 'accesses '.$dir.', time desc';
break;
case 'ip':
$sort_sql = 'ip '.$dir.', time desc';
break;
default:
$sort = 'time';
$sort_sql = 'time '.$dir;
break;
}
set_pref('smd_akey_sort_column', $sort, 'smd_akey', PREF_HIDDEN, '', 0, PREF_PRIVATE);
set_pref('smd_akey_sort_dir', $dir, 'smd_akey', PREF_HIDDEN, '', 0, PREF_PRIVATE);
$switch_dir = ($dir == 'desc') ? 'asc' : 'desc';
$criteria = 1;
if ($search_method and $crit) {
$crit_escaped = doSlash(str_replace(array('\\','%','_','\''), array('\\\\','\\%','\\_', '\\\''), $crit));
$critsql = array(
'page' => "page like '%$crit_escaped%'",
'triggah' => "triggah like '%$crit_escaped%'",
'maximum' => "maximum = '$crit_escaped'",
'accesses' => "accesses = '$crit_escaped'",
'ip' => "ip like '%$crit_escaped%'",
);
if (array_key_exists($search_method, $critsql)) {
$criteria = $critsql[$search_method];
$limit = 500;
} else {
$search_method = '';
$crit = '';
}
} else {
$search_method = '';
$crit = '';
}
$total = safe_count(SMD_AKEYS, "$criteria");
echo '
';
if ($total < 1) {
if ($criteria != 1) {
echo n.smd_akey_search_form($crit, $search_method).
n.graf(gTxt('no_results_found'), ' class="indicator"').'
';
return;
}
}
$limit = max($smd_akey_list_pageby, 15);
list($page, $offset, $numPages) = pager($total, $limit, $page);
echo n.smd_akey_search_form($crit, $search_method).'';
// Retrieve the secret keyring table entries
$secring = safe_rows('*', SMD_AKEYS, "$criteria order by $sort_sql limit $offset, $limit");
// Set up the buttons and column info
$newbtn = ''.smd_akey_gTxt('smd_akey_btn_new').'';
$prefbtn = ''.smd_akey_gTxt('smd_akey_btn_pref').'';
$showip = get_pref('smd_akey_log_ip', $smd_akey_prefs['smd_akey_log_ip']['default'], 1);
echo <<
function smd_akey_togglenew() {
box = jQuery("#smd_akey_create");
if (box.css("display") == "none") {
box.show();
} else {
box.hide();
}
jQuery("input.smd_focus").focus();
return false;
}
jQuery(function() {
jQuery("#smd_akey_add").click(function () {
jQuery("#smd_akey_step").val('smd_akey_create');
jQuery("#smd_akey_form").removeAttr('onsubmit').submit();
});
});
EOC;
// Inject styles
echo '';
// Access key list
echo n.'';
echo '
';
echo '
'.
n.nav_form($smd_akey_event, $page, $numPages, $sort, $dir, $crit, $search_method, $total, $limit).
n.pageby_form($smd_akey_event, $smd_akey_list_pageby).
n.'
'.n.'
';
} else {
// Table not installed
$btnInstall = '';
$btnStyle = ' style="border:0;height:25px"';
echo startTable('list');
echo tr(tda(strong(smd_akey_gTxt('smd_akey_prefs_some_tbl')).br.br
.smd_akey_gTxt('smd_akey_prefs_some_explain').br.br
.smd_akey_gTxt('smd_akey_prefs_some_opts'), ' colspan="2"')
);
echo tr(tda($btnInstall, $btnStyle));
echo endTable();
}
}
// Change and store qty-per-page value
function smd_akey_change_pageby() {
event_change_pageby('smd_akey');
smd_akey();
}
// The search dropdown list
function smd_akey_search_form($crit, $method) {
global $smd_akey_event, $smd_akey_prefs;
$doip = get_pref('smd_akey_log_ip', $smd_akey_prefs['smd_akey_log_ip']['default'], 1);
$methods = array(
'page' => smd_akey_gTxt('smd_akey_page'),
'triggah' => smd_akey_gTxt('smd_akey_trigger'),
'maximum' => smd_akey_gTxt('smd_akey_max'),
'accesses' => smd_akey_gTxt('smd_akey_accesses'),
);
if ($doip) {
$methods['ip'] = gTxt('IP');
}
return search_form($smd_akey_event, '', $crit, $methods, $method, 'page');
}
// Create a key from the admin side's 'New key' button
function smd_akey_create() {
extract(gpsa(array('smd_akey_newpage', 'smd_akey_triggah', 'smd_akey_time', 'smd_akey_expires', 'smd_akey_maximum')));
if ($smd_akey_newpage) {
// Just call the public tag with the relevant options
$key = smd_access_key(
array(
'url' => $smd_akey_newpage,
'trigger' => $smd_akey_triggah,
'start' => $smd_akey_time,
'expires' => $smd_akey_expires,
'max' => $smd_akey_maximum,
)
);
$msg = smd_akey_gTxt('smd_akey_generated', array('{key}' => $key));
} else {
$msg = array(smd_akey_gTxt('smd_akey_need_page'), E_ERROR);
}
smd_akey($msg);
}
// Handle submission of the multi-edit dropdown options
function smd_akey_multi_edit() {
$selected = gps('selected');
$operation = gps('smd_akey_multi_edit');
$del = 0;
$msg = '';
switch ($operation) {
case 'smd_akey_delete':
if ($selected) {
foreach ($selected as $sel) {
$parts = explode('|', $sel);
$ret = safe_delete(SMD_AKEYS, "page = '" . $parts[0] . "' AND t_hex = '" . $parts[1] . "'");
$del = ($ret) ? $del+1 : $del;
}
$msg = smd_akey_gTxt('smd_akey_deleted', array('{deleted}' => $del));
}
break;
}
smd_akey($msg);
}
// Display the prefs
function smd_akey_prefs() {
global $smd_akey_event, $smd_akey_prefs;
pagetop(smd_akey_gTxt('smd_akey_pref_legend'));
$out = array();
$out[] = '';
echo join(n, $out);
}
// Save the prefs
function smd_akey_prefsave() {
global $smd_akey_event, $smd_akey_prefs;
foreach ($smd_akey_prefs as $idx => $prefobj) {
$val = ps($idx);
set_pref($idx, $val, $smd_akey_event, $prefobj['type'], $prefobj['html'], $prefobj['position']);
}
$msg = smd_akey_gTxt('smd_akey_prefs_saved');
smd_akey($msg);
}
// Add akey table if not already installed
function smd_akey_table_install($showpane='1') {
$GLOBALS['txp_err_count'] = 0;
$ret = '';
$sql = array();
// Use 'triggah' and 'maximum' because 'trigger' and 'max' are reserved words.
$sql[] = "CREATE TABLE IF NOT EXISTS `".PFX.SMD_AKEYS."` (
`page` varchar(255) NOT NULL default '',
`t_hex` varchar(17) NOT NULL default '',
`time` int(14) NOT NULL default 0,
`secret` varchar(255) NOT NULL default '',
`triggah` varchar(255) NULL default '',
`maximum` int(11) NULL default 0,
`accesses` int(11) NULL default 0,
`ip` text NOT NULL default '',
PRIMARY KEY (`page`,`t_hex`)
) ENGINE=MyISAM";
if(gps('debug')) {
dmp($sql);
}
foreach ($sql as $qry) {
$ret = safe_query($qry);
if ($ret===false) {
$GLOBALS['txp_err_count']++;
echo "".$GLOBALS['txp_err_count'].". ".mysql_error()."
\n";
echo "\n";
}
}
// Spit out results
if ($GLOBALS['txp_err_count'] == 0) {
if ($showpane) {
$msg = smd_akey_gTxt('smd_akey_tbl_installed');
smd_akey($msg);
}
} else {
if ($showpane) {
$msg = smd_akey_gTxt('smd_akey_tbl_not_installed');
smd_akey($msg);
}
}
}
// ------------------------
// Drop table if in database
function smd_akey_table_remove() {
$ret = '';
$sql = array();
$GLOBALS['txp_err_count'] = 0;
if (smd_akey_table_exist()) {
$sql[] = "DROP TABLE IF EXISTS " .PFX.SMD_AKEYS. "; ";
if(gps('debug')) {
dmp($sql);
}
foreach ($sql as $qry) {
$ret = safe_query($qry);
if ($ret===false) {
$GLOBALS['txp_err_count']++;
echo "".$GLOBALS['txp_err_count'].". ".mysql_error()."
\n";
echo "\n";
}
}
}
if ($GLOBALS['txp_err_count'] == 0) {
$msg = smd_akey_gTxt('smd_akey_tbl_removed');
} else {
$msg = smd_akey_gTxt('smd_akey_tbl_not_removed');
smd_akey($msg);
}
}
// ------------------------
function smd_akey_table_exist($type='') {
global $plugins_ver, $DB;
// Upgrade check
$ver = get_pref('smd_akey_installed_version', '');
if (!$ver || $plugins_ver['smd_access_keys'] != $ver) {
// Increase the size of the t_hex field to allow for expiry times
$ret = @safe_field("CHARACTER_MAXIMUM_LENGTH", "INFORMATION_SCHEMA.COLUMNS", "table_name = '".PFX.SMD_AKEYS."' AND table_schema = '" . $DB->db . "' AND column_name = 't_hex'");
if ($ret != '17') {
safe_alter(SMD_AKEYS, "CHANGE `t_hex` `t_hex` VARCHAR( 17 ) NOT NULL DEFAULT ''");
set_pref('smd_akey_installed_version', $plugins_ver['smd_access_keys'], 'smd_akey', PREF_HIDDEN, '', 0);
}
}
if ($type == '1') {
$tbls = array(SMD_AKEYS => 8);
$out = count($tbls);
foreach ($tbls as $tbl => $cols) {
if (count(@safe_show('columns', $tbl)) == $cols) {
$out--;
}
}
return ($out===0) ? 1 : 0;
} else {
return(@safe_show('columns', SMD_AKEYS));
}
}
//**********************
// PUBLIC SIDE INTERFACE
//**********************
function smd_access_key($atts, $thing=NULL) {
global $smd_akey_prefs, $smd_akey_info;
// In case this tag is called from the admin side - needs parse()
include_once txpath.'/publish.php';
extract(lAtts(array(
'secret' => '',
'url' => '',
'site_name' => '1',
'section_mode' => '0',
'start' => '',
'expires' => '',
'trigger' => 'smd_akey',
'max' => '',
'extra' => '',
'form' => '',
),$atts));
if (smd_akey_table_exist(1)) {
$thing = (empty($form)) ? $thing : fetch_form($form);
$thing = (empty($thing)) ? '' : $thing;
$trigger = trim($trigger);
$trigger = ($trigger == 'file_download') ? '' : $trigger;
$smd_akey_salt_length = get_pref('smd_akey_salt_length', $smd_akey_prefs['smd_akey_salt_length']['default']);
// Without a URL, assume current page
$page = rtrim( (($url) ? $url : serverSet('REQUEST_URI')), '/');
if ($site_name && (strpos($page, 'http') !== 0)) {
// Can't use raw hu since it contains the subdir (as does the REQUEST_URI)
// so duplicate portions would occur in the generated URL
$urlparts = parse_url(hu);
$page = $urlparts['scheme'] . '://' . $urlparts['host'] . $page;
}
if (!$secret) {
$secret = uniqid('', true);
}
$salt = substr(md5(uniqid(rand(), true)), 0, $smd_akey_salt_length);
$plen = strlen($page) % 32; // Because 32 is the size of an md5 string and we don't want to fall off the end
// Generate a timestamp. The clock starts ticking from this moment
$ts = ($start) ? safe_strtotime($start) : time();
$ts = ($ts === false) ? time() : $ts;
$t_hex = dechex($ts);
// Any expiry to add?
if ($expires) {
// Relative offset from the start time, or an absolute expiry?
$rel = (strpos($expires, '+') === 0) ? true : false;
if ($rel) {
$exp = safe_strtotime($expires, $ts);
} else {
$exp = safe_strtotime($expires);
}
if ($exp !== false) {
$t_hex .= '-' . dechex($exp);
}
}
// Update/insert the remaining data
$exists = safe_field('page', SMD_AKEYS, "page='".doSlash($page)."' AND t_hex='".doSlash($t_hex)."'");
$maxinfo = '';
if ($max) {
$maxinfo = ", maximum = '".doSlash($max)."', accesses = '0'";
}
if ($exists) {
safe_update(SMD_AKEYS, "triggah='".doSlash($trigger)."', time='".doSlash($ts)."', secret='".doSlash($secret)."'" . $maxinfo, "page='".doSlash($page)."' AND t_hex='".doSlash($t_hex)."'");
} else {
safe_insert(SMD_AKEYS, "page='".doSlash($page)."', t_hex='".doSlash($t_hex)."', triggah='".doSlash($trigger)."', secret='".doSlash($secret)."', time='".doSlash($ts)."'" . $maxinfo);
}
// Tack on max if applicable
$max_safe = $max;
$max = ($max) ? '.'.$max : '';
// And any extra
$extratok = ($extra) ? '/'.$extra : '';
// Create the raw token...
$token = md5($salt.$secret.$page.$trigger.$t_hex.$max.$extra);
// ... and insert the salt partway through
$salty_token = substr($token, 0, $plen) . $salt . substr($token, $plen);
$tokensep = ($section_mode) ? '?' : '/';
$key = $page . (($trigger) ? $tokensep . $trigger : '') . '/' . $salty_token . '/' . $t_hex . $max . $extratok;
$smd_akey_info = array(
'ak_page' => $page,
'ak_extra' => $extra,
'ak_hextime' => $t_hex,
'ak_issued' => $ts,
'ak_now' => time(),
'ak_expires' => ($expires) ? $exp : '',
'ak_trigger' => $trigger,
'ak_maximum' => $max_safe,
'ak_separator' => $tokensep,
'ak_key' => $key,
);
return parse($thing);
} else {
trigger_error(smd_akey_gTxt('smd_akey_tbl_not_installed'), E_USER_NOTICE);
}
}
// Protect a page for a given time limit from the moment the
// access token has been generated. Embed this tag at the top
// of the page you want to protect or wrap it around part of a
// page you wish to protect. The unique URL to the resource
// is generated by
function smd_access_protect($atts, $thing=NULL) {
global $smd_access_error, $smd_access_errcode, $smd_akey_protected_info, $smd_akey_prefs, $permlink_mode, $plugins;
extract(lAtts(array(
'trigger' => 'smd_akey',
'trigger_mode' => 'exact', // exact, begins, ends, contains
'site_name' => '1',
'section_mode' => '0',
'force' => '0',
'expires' => '3600', // in seconds
),$atts));
if (smd_akey_table_exist(1)) {
$url = serverSet('REQUEST_URI');
if ($site_name && (strpos($url, hu) === false)) {
$urlparts = parse_url(hu);
// Can't use raw hu since it contains the subdir (as does the REQUEST_URI)
// so duplicates would occur in the generated URL
$url = $urlparts['scheme'] . '://' . $urlparts['host'] . $url;
}
if ($section_mode == '1') {
$halves = explode('?', $url);
$half1 = explode('/', $halves[0]);
$half2 = (isset($halves[1])) ? explode('/', $halves[1]) : array();
$parts = array_merge($half1, $half2);
} else {
$parts = explode('/', $url);
}
trace_add('[smd_access_key URL elements: ' . join('|', $parts).']');
// Look for one of the triggers in the URL and bomb out if we find it
$triggers = do_list($trigger);
$trigger = $triggers[0]; // Initialise to the first value in case no others are found
$trigoff = false;
foreach ($triggers as $trig) {
switch ($trigger_mode) {
case 'exact':
$trigoff = array_search($trig, $parts);
$realTrig = $trig;
break;
case 'begins':
$count = 0;
foreach ($parts as $part) {
if (strpos($part, $trig) === 0) {
$trigoff = $count;
$realTrig = $part;
break;
}
$count++;
}
break;
case 'ends':
$count = 0;
foreach ($parts as $part) {
$re = '/.+'.preg_quote($trig).'$/i';
if (preg_match($re, $part) === 1) {
$trigoff = $count;
$realTrig = $part;
break;
}
$count++;
}
break;
case 'contains':
$count = 0;
foreach ($parts as $part) {
$re = '/.*'.preg_quote($trig).'.*$/i';
if (preg_match($re, $part) === 1) {
$trigoff = $count;
$realTrig = $part;
break;
}
$count++;
}
break;
}
if ($trigoff !== false) {
// Found it so set the trigger to be the current item and jump out
$trigoff = ($trigger == 'file_download') ? $trigoff + 2 : $trigoff;
$trigger = $realTrig;
break;
}
}
trace_add('[smd_access_key trigger: ' . $trigger . ($trigoff ? ' found at ' . $trigoff : '') . ']');
$ret = false;
$smd_access_error = $smd_access_errcode = '';
$smd_akey_salt_length = get_pref('smd_akey_salt_length', $smd_akey_prefs['smd_akey_salt_length']['default']);
$doip = get_pref('smd_akey_log_ip', $smd_akey_prefs['smd_akey_log_ip']['default']);
if ($trigoff !== false) {
$tokidx = $trigoff + 1;
$timeidx = $trigoff + 2;
$extraidx = $trigoff + 3;
// OK, on a trigger page, so read the token from the URL
$tok = (isset($parts[$tokidx]) && strlen($parts[$tokidx]) == intval(32 + $smd_akey_salt_length)) ? $parts[$tokidx] : 0;
trace_add('[smd_access_key token: ' . $tok .']');
if ($tok) {
// The token is present, so read the timestamp from the URL
$t_hex = (isset($parts[$timeidx])) ? $parts[$timeidx] : 0;
// Is there a download limit? Extract it if so
$timeparts = do_list($t_hex, '.');
$max = (isset($timeparts[1])) ? $timeparts[1] : '0';
$maxtok = ($max) ? '.'.$max : '';
$t_hex = $timeparts[0];
// Any extra info?
$extras = (isset($parts[$extraidx])) ? array_slice($parts, $extraidx) : array();
// Recreate the original page URL, sans /trigger/token/time
if ($trigger == 'file_download') {
$trigoff++;
$trigger = '';
}
// gbp_permanent_links sets messy mode behind the scenes but still uses non-messy URLs
// so it requires an exception
$gbp_pl = (is_array($plugins) && in_array('gbp_permanent_links', $plugins));
if ($permlink_mode == 'messy' && !$gbp_pl) {
// Don't want a slash between site and start of query params
$page = rtrim(join('/', array_slice($parts, 0, $trigoff-1)), '/') . $parts[$trigoff-1];
} else {
$page = rtrim(join('/', array_slice($parts, 0, $trigoff)), '/');
}
// In case the URL contains non-ascii chars
$page = rawurldecode($page);
trace_add('[smd_access_key page | timestamp | max | extras: ' . join('|', array($page, $t_hex, $max, $extras)) . ']');
if ($t_hex) {
// The timestamp is present. Next, get the secret key
$secret = false;
$secring = safe_row('*', SMD_AKEYS, "page='".doSlash($page)."' AND t_hex = '".doSlash($t_hex)."'");
if ($secring) {
$secret = $secring['secret'];
// Extract the salt from the token
$plen = strlen($page) % 32;
$salt = substr($tok, $plen, $smd_akey_salt_length);
$tok = substr($tok, 0, $plen).substr($tok, $plen+$smd_akey_salt_length);
$ext = (($extras) ? urldecode(join('/', $extras)) : '');
// Regenerate the original token...
$check_token = md5($salt.$secret.$page.$trigger.$t_hex.$maxtok.$ext);
trace_add('[smd_access_key reconstructed token: ' . $check_token . ']');
// ... and compare it to the one in the URL
if ($check_token == $tok) {
// Token is valid. Now check if the page has expired
// Is there an explicit access key expiry? Extract that if so
$timeparts = do_list($t_hex, '-');
$t_exp = (isset($timeparts[1])) ? hexdec($timeparts[1]) : '';
$t_beg = $timeparts[0];
$t_dec = hexdec($t_beg);
$now = time();
// Has the resource become available yet?
if ($now < $t_dec) {
if ($thing == NULL) {
txp_die(smd_akey_gTxt('smd_akey_err_unavailable'), 410);
} else {
$smd_access_error = 'smd_akey_err_unavailable';
$smd_access_errcode = 410;
}
} else {
// Has token's expiry been reached, or is 'now' greater than 'then' (when token generated) + expiry period?
if ($t_exp) {
$tester = true;
$compare_to = $t_exp;
} else {
$tester = ($expires != 0);
$compare_to = $t_dec + $expires;
}
if ($tester && ($now > $compare_to)) {
if ($thing == NULL) {
txp_die(smd_akey_gTxt('smd_akey_err_expired'), 410);
} else {
$smd_access_error = 'smd_akey_err_expired';
$smd_access_errcode = 410;
}
} else {
// Check if the download limit has been exceeded
$vu_qty = $secring['accesses'];
if ($max) {
if ($vu_qty < $max) {
$ret = true;
} else {
if ($thing == NULL) {
txp_die(smd_akey_gTxt('smd_akey_err_limit'), 410);
} else {
$smd_access_error = 'smd_akey_err_limit';
$smd_access_errcode = 410;
}
}
} else {
$ret = true;
}
// Increment the access counter
$vu_qty++;
// Grab the IP and add it to the list of IPs so far
if ($doip) {
$ips = do_list($secring['ip'], ' ');
$ip = remote_addr();
if (!in_array($ip, $ips)) {
$ips[] = $ip;
}
$ipup = ", ip='".doSlash(trim(join(' ', $ips)))."'";
} else {
$ipup = '';
}
safe_update(SMD_AKEYS, "accesses='".doSlash($vu_qty)."'" . $ipup, "page='".doSlash($page)."' AND t_hex = '".doSlash($t_hex)."'");
// Load up the global array so and work
$smd_akey_protected_info = array(
'page' => $secring['page'],
'hextime' => $secring['t_hex'],
'issued' => $secring['time'],
'now' => $now,
'expires' => $compare_to,
'trigger' => $secring['triggah'],
'maximum' => $secring['maximum'],
'accesses' => $vu_qty,
);
if ($doip) {
$smd_akey_protected_info['ip'] = $ip;
}
if ($extras) {
$smd_akey_protected_info['extra'] = urldecode(join('/', $extras));
foreach($extras as $idx => $extra) {
$smd_akey_protected_info['extra_'.intval($idx+1)] = urldecode($extra);
}
}
}
}
} else {
if ($thing == NULL) {
txp_die(smd_akey_gTxt('smd_akey_err_invalid_token'), 403);
} else {
$smd_access_error = 'smd_akey_err_invalid_token';
$smd_access_errcode = 403;
}
}
} else {
if ($thing == NULL) {
txp_die(smd_akey_gTxt('smd_akey_err_unauthorized'), 401);
} else {
$smd_access_error = 'smd_akey_err_unauthorized';
$smd_access_errcode = 401;
}
}
} else {
if ($thing == NULL) {
txp_die(smd_akey_gTxt('smd_akey_err_missing_timestamp'), 403);
} else {
$smd_access_error = 'smd_akey_err_missing_timestamp';
$smd_access_errcode = 403;
}
}
} else {
if ($thing == NULL) {
txp_die(smd_akey_gTxt('smd_akey_err_bad_token'), 403);
} else {
$smd_access_error = 'smd_akey_err_bad_token';
$smd_access_errcode = 403;
}
}
} else {
// If we always want to forbid access to this page regardless if the trigger exists
if ($force) {
if ($thing == NULL) {
txp_die(smd_akey_gTxt('smd_akey_err_forbidden'), 401);
} else {
$smd_access_error = 'smd_akey_err_forbidden';
$smd_access_errcode = 401;
}
} else {
$ret = true;
}
}
if ($smd_access_error || $smd_access_errcode) {
trace_add('[smd_access_key error state: ' . $smd_access_errcode . '|' . $smd_access_error . ']');
}
// If we reach this point it's because we're using a container
return parse(EvalElse($thing, $ret));
} else {
trigger_error(smd_akey_gTxt('smd_akey_tbl_not_installed'), E_USER_NOTICE);
}
}
// Called just before a download is initiated
function smd_access_protect_download($evt, $stp) {
global $smd_akey_prefs, $id, $file_error;
if (smd_akey_table_exist(1) && !isset($file_error)) {
$fileid = intval($id);
// In case the page was called with a bogus filename, get the "true" filename
// from the database and make up the valid URL
$real_file = safe_field("filename", "txp_file", "id=".doSlash($fileid));
$page = filedownloadurl($fileid, $real_file);
$secring = safe_field('page', SMD_AKEYS, "page='".doSlash($page)."'");
// Only want to protect pages that we've generated tokens for
if ($secring) {
// Pass in a default expiry from the pref, but it can be overridden by the key's expiry
return smd_access_protect(
array(
'trigger' => 'file_download',
'force' => '1',
'expires' => get_pref('smd_akey_file_download_expires', $smd_akey_prefs['smd_akey_file_download_expires']['default']),
)
);
}
}
// remote download not done - leave to Txp to handle error or "local" file download
return;
}
// Conditional tag for checking error status from smd_access_protect
function smd_if_access_error($atts, $thing=NULL) {
global $smd_access_error, $smd_access_errcode;
extract(lAtts(array(
'type' => '',
'code' => '',
),$atts));
$err = array();
$codes = do_list($code);
$types = do_list($type);
if ($smd_access_error) {
if ($code && $type) {
$err['code'] = (in_array($smd_access_errcode, $codes)) ? true : false;
$err['msg'] = (in_array($smd_access_error, $types)) ? true : false;
} else if ($code) {
$err['code'] = (in_array($smd_access_errcode, $codes)) ? true : false;
} else if ($type) {
$err['msg'] = (in_array($smd_access_error, $types)) ? true : false;
} else {
$err['msg'] = true;
}
}
$out = in_array(false, $err) ? false : true; // AND logic
return parse(EvalElse($thing, $out));
}
// Display access error information
function smd_access_error($atts, $thing=NULL) {
global $smd_access_error, $smd_access_errcode;
extract(lAtts(array(
'item' => 'message',
'message' => '',
'wraptag' => '',
'class' => '',
'html_id' => '',
'break' => '',
'breakclass' => '',
),$atts));
$out = array();
$items = do_list($item);
if ($smd_access_errcode && in_array('code', $items)) {
$out[] = $smd_access_errcode;
}
if ($smd_access_error && in_array('message', $items)) {
$out[] = ($message) ? $message : smd_akey_gTxt($smd_access_error);
}
if ($out) {
return doWrap($out, $wraptag, $break, $class, $breakclass, '', '', $html_id);
}
return '';
}
// Display access information for custom formatted messages
function smd_access_info($atts, $thing=NULL) {
global $smd_akey_protected_info, $smd_akey_info;
extract(lAtts(array(
'item' => 'page',
'escape' => 'html',
'format' => '%Y-%m-%d %H:%M:%S',
'wraptag' => '',
'class' => '',
'html_id' => '',
'break' => '',
'breakclass' => '',
),$atts));
$out = array();
$items = do_list($item);
foreach ($items as $idx) {
$ak_idx = 'ak_'.$idx;
if ($smd_akey_protected_info && array_key_exists($idx, $smd_akey_protected_info)) {
$val = ($escape == 'html') ? htmlspecialchars($smd_akey_protected_info[$idx]) : $smd_akey_protected_info[$idx];
if (in_array($idx, array('time', 'now', 'expires')) && $format) {
$val = safe_strftime($format, $val);
}
$out[] = $val;
}
if ($smd_akey_info && array_key_exists($ak_idx, $smd_akey_info)) {
$val = ($escape == 'html') ? htmlspecialchars($smd_akey_info[$ak_idx]) : $smd_akey_info[$ak_idx];
if (in_array($idx, array('time', 'now', 'expires')) && $format) {
$val = safe_strftime($format, $val);
}
$out[] = $val;
}
}
if ($out) {
return doWrap($out, $wraptag, $break, $class, $breakclass, '', '', $html_id);
}
return '';
}
/**
* smd_akey_gTxt Convert strings for i18n purposes
*
* @param string $what [+] [private] [static] Name of the item from the array to retrieve
* @param array $atts [+] (Default: Array) Array of 'search' => 'replacements' to make
*/
function smd_akey_gTxt($what, $atts = array()) {
$lang = array(
'en-gb' => array(
'smd_akey_accesses' => 'Access attempts',
'smd_akey_btn_new' => 'New key',
'smd_akey_btn_pref' => 'Prefs',
'smd_akey_deleted' => 'Keys deleted: {deleted}',
'smd_akey_err_bad_token' => 'Missing or mangled access key',
'smd_akey_err_expired' => 'Access expired',
'smd_akey_err_forbidden' => 'Forbidden access',
'smd_akey_err_invalid_token' => 'Invalid access key',
'smd_akey_err_missing_timestamp' => 'Missing timestamp',
'smd_akey_err_unavailable' => 'Not available',
'smd_akey_err_unauthorized' => 'Unauthorized access',
'smd_akey_err_limit' => 'Access limit reached',
'smd_akey_file_download_expires' => 'File download expiry time (seconds)',
'smd_akey_generated' => 'Access key: {key}',
'smd_akey_log_ip' => 'Log IP addresses',
'smd_akey_max' => 'Maximum',
'smd_akey_need_page' => 'You need to enter a page URL',
'smd_akey_page' => 'Page',
'smd_akey_pref_legend' => 'Access key preferences',
'smd_akey_prefs_saved' => 'Preferences saved',
'smd_akey_prefs_some_tbl' => 'Not all table info available.',
'smd_akey_prefs_some_explain' => 'This is either a new installation or a different version'.br.'of the plugin to one you had before.',
'smd_akey_prefs_some_opts' => 'Click "Install table" to add or update the table'.br.'leaving all existing data untouched.',
'smd_akey_prefs_some_tbl' => 'Not all table info available.',
'smd_akey_salt_length' => 'Salt length (characters)',
'smd_akey_tab_name' => 'Access keys',
'smd_akey_tbl_install_lbl' => 'Install table',
'smd_akey_tbl_installed' => 'Table installed',
'smd_akey_tbl_not_installed' => 'Table not installed',
'smd_akey_tbl_removed' => 'Table removed',
'smd_akey_tbl_not_removed' => 'Table not removed',
'smd_akey_time' => 'Issued',
'smd_akey_trigger' => 'Trigger',
),
);
$thislang = get_pref('language', 'en-gb');
$thislang = (isset($lang[$thislang][$what])) ? $thislang : 'en-gb';
return strtr($lang[$thislang][$what], $atts);
}