<?php
require_once($INC . "/util.inc.php");
require_once($INC . "/log.inc.php");
require_once($INC . "/sql.inc.php");
require_once($INC . "/user.inc.php");
require_once($INC . "/git.inc.php");
$rg_rights = array();
$rg_rights_default = array();
$rg_rights_cmp_func = array();
$rg_rights_inject = array();
$rg_rights_error = "";
function rg_rights_set_error($str)
{
global $rg_rights_error;
$rg_rights_error = $str;
rg_log($str);
}
function rg_rights_error()
{
global $rg_rights_error;
return $rg_rights_error;
}
/*
* Register a set of rights
*/
function rg_rights_register($type, $rights, $default_rights, $cmp_func,
$inject_func)
{
global $rg_rights;
global $rg_rights_default;
global $rg_rights_cmp_func;
global $rg_rights_inject;
$rg_rights[$type] = $rights;
$rg_rights_default[$type] = $default_rights;
$rg_rights_cmp_func[$type] = $cmp_func;
if ($inject_func !== FALSE)
$rg_rights_inject[$type] = $inject_func;
}
/*
* Enforce correct chars
*/
function rg_rights_fix($rights)
{
return preg_replace("/[^A-Za-z0-9]/", "", $rights);
}
/*
* Combine two repo rights strings
*/
function rg_rights_combine($a, $b)
{
$len = strlen($b);
for ($i = 0; $i < $len; $i++)
if (!strstr($a, $b[$i]))
$a .= $b[$i];
return $a;
}
/*
* Returns all possible rights
*/
function rg_rights_all($type)
{
global $rg_rights;
if (!isset($rg_rights[$type])) {
rg_log("WARN: type [$type] is not registered!");
return "";
}
$ret = "";
foreach ($rg_rights[$type] as $letter => $junk)
$ret = rg_rights_combine($ret, $letter);
return $ret;
}
/*
* Returns default rights for a type
*/
function rg_rights_default($type)
{
global $rg_rights_default;
if (!isset($rg_rights_default[$type])) {
rg_log("WARN: type [$type] is not registered!");
return "";
}
return $rg_rights_default[$type];
}
/*
* Rights -> form
*/
function rg_rights_checkboxes($type, $name, $passed_rights)
{
global $rg_rights;
if (!isset($rg_rights[$type])) {
rg_internal_error("[$type] is not registered!");
return "";
}
$ret = "";
$br = "";
foreach ($rg_rights[$type] as $right => $info) {
$add = "";
if (strstr($passed_rights, $right))
$add = " checked=\"checked\"";
$ret .= $br
. "<input type=\"checkbox\""
. " name=\"" . $name . "[$right]\""
. " id=\"" . $name . "[$right]\""
. $add . " />"
. "<label for=\"" . $name . "[$right]\">" . $info . "</label>"
. "\n";
$br = "<br />\n";
}
return $ret;
}
/*
* List rights as text
*/
function rg_rights_text($type, $rights)
{
global $rg_rights;
$ret = array();
$all = rg_rights_all($type);
if (strcmp($rights, $all) == 0) {
$ret[] = "All";
return $ret;
}
$len = strlen($rights);
if ($len == 0)
return array("None");
for ($i = 0; $i < $len; $i++) {
if (isset($rg_rights[$type][$rights[$i]]))
$ret[] = $rg_rights[$type][$rights[$i]];
else
$ret[] = "?" . $rights[$i] . "?";
}
return $ret;
}
/*
* Transforms rights array into a string
*/
function rg_rights_a2s($a)
{
$rights = "";
if (empty($a))
return "";
if (!is_array($a)) {
rg_internal_error("Rights array is not an array");
return "";
}
foreach ($a as $right => $junk)
$rights .= $right;
return rg_rights_fix($rights);
}
/*
* Improves a little bit the items of a right
* TODO: we have a circular dependency on user.inc. Remove the lookup and break
* the dependency. Register a function globally, from user.inc, that will be
* called here.
*/
function rg_rights_cosmetic($db, &$row)
{
if ($row['uid'] == 0) {
$row['username'] = "*";
} else {
$_ui = rg_user_info($db, $row['uid'], "", "");
if ($_ui['exists'] == 1)
$row['username'] = $_ui['username'];
else
$row['username'] = "?";
}
if ($row['who'] == 0) {
$row['who_name'] = "*";
} else {
$_ui = rg_user_info($db, $row['who'], "", "");
if ($_ui['exists'] == 1)
$row['who_name'] = $_ui['username'];
else
$row['who_name'] = "?";
}
$_r = rg_rights_text($row['type'], $row['rights']);
$row['rights_text'] = implode(", ", $_r);
if ($row['itime'] == 0)
$row['itime_text'] = "N/A";
else
$row['itime_text'] = gmdate("Y-m-d H:i", $row['itime']);
if (strcmp($row['ip'], "*") == 0)
$row['ip'] = "";
if (empty($row['ip']))
$row['ip_nice'] = "Any";
else
$row['ip_nice'] = $row['ip'];
if (!isset($row['description']))
$row['description'] = "";
$_a = rg_xss_safe(trim($row['description']));
$row['HTML:description_nlbr'] = nl2br($_a);
if (isset($row['prio'])) {
if (($row['prio'] >= 10) && ($row['prio'] <= 30000))
$row['can_be_deleted'] = 1;
else
$row['can_be_deleted'] = 0;
}
}
/*
* Returns the rights from db
*/
function rg_rights_load($db, $obj_id, $type)
{
rg_prof_start("rights_load");
rg_log_enter("rights_load: obj_id=$obj_id type=$type");
$type = rg_force_alphanum($type);
$ret = FALSE;
while (1) {
$key = "rights_by_obj_id::$obj_id::$type";
$r = rg_cache_get($key);
if ($r !== FALSE) {
$ret = $r;
break;
}
$params = array("type" => $type, "obj_id" => $obj_id);
$sql = "SELECT * FROM rights"
. " WHERE type = @@type@@"
. " AND obj_id = @@obj_id@@"
. " ORDER BY prio, itime";
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
rg_rights_set_error("cannot get info");
break;
}
// TODO: we have a problem when we delete a right: we
// have to invalidate them all. Index by right_id!
$ret = array();
while (($row = rg_sql_fetch_array($res)))
$ret[] = $row;
rg_sql_free_result($res);
rg_cache_set($key, $ret, RG_SOCKET_NO_WAIT);
break;
}
rg_log_exit();
rg_prof_end("rights_load");
return $ret;
}
/*
* Helper for rg_rights_sort
*/
function rg_rights_sort_helper($a, $b)
{
if ($a['prio'] > $b['prio'])
return 1;
if ($a['prio'] == $b['prio'])
return 0;
return -1;
}
/*
* Get rights for an object
* @uid - the uid of the (normally) logged in user. If -1, do not filter by uid.
* @right_id - optional id (used by edit)
*/
function rg_rights_get($db, $obj_id, $type, $owner, $uid, $right_id)
{
global $rg_rights;
global $rg_rights_inject;
rg_prof_start('rights_get');
rg_log_enter("rg_rights_get: obj_id=$obj_id type=$type owner=$owner"
. " uid=$uid right_id=$right_id");
$ret = array();
$ret['ok'] = 0;
$ret['list'] = array();
while (1) {
$r = rg_rights_load($db, $obj_id, $type);
if ($r === FALSE)
break;
// Inject rights for owner
if ($owner > 0) {
$a = array();
$a['type'] = $type;
$a['obj_id'] = $obj_id;
$a['uid'] = $owner;
$a['itime'] = 0;
$a['misc'] = '';
$a['prio'] = 0;
$a['who'] = $owner;
$a['right_id'] = 0;
$a['ip'] = '';
$a['can_be_deleted'] = 0;
$a['rights'] = rg_rights_all($type);
$a['description'] = 'Autogenerated (owner)';
$r[] = $a;
}
// Inject specific rights
if (isset($rg_rights_inject[$type])) {
$f = $rg_rights_inject[$type];
$rows = $f($db, $obj_id, $type, $owner, $uid);
//rg_log_ml("CHECK: inject function for '$type' [$f] returned: " . print_r($rows, TRUE));
foreach ($rows as $row) {
//rg_log_ml("rights_get: inject specific rights: " . print_r($row, TRUE));
$r[] = $row;
}
}
// now, filter by uid and right_id
$r2 = array();
foreach ($r as $k => $v) {
if (($right_id > 0) && ($v['right_id'] != $right_id))
continue;
if (($uid == -1) || ($v['uid'] == $uid) || ($v['uid'] == 0))
$r2[] = $v;
}
// sorting by prio
uasort($r2, 'rg_rights_sort_helper');
// cosmetic
//rg_log_ml('DEBUG: before cosmetic: ' . print_r($r2, TRUE));
foreach ($r2 as $index => &$row)
rg_rights_cosmetic($db, $row);
$ret['list'] = $r2;
$ret['ok'] = 1;
break;
}
//rg_log("rights_get: rights=" . rg_array2string($ret['list']));
rg_log_exit();
rg_prof_end('rights_get');
return $ret;
}
/*
* Set rights for an object
*/
function rg_rights_set($db, $type, $a)
{
rg_prof_start("rights_set");
rg_log_enter("rg_rights_set: type=$type a=" . rg_array2string($a));
$ret = FALSE;
while (1) {
if (($a['prio'] < 10) || ($a['prio'] > 30000)) {
rg_rights_set_error("prio must be between 10 and 30000");
break;
}
$r = rg_rights_validate_ip($a['ip']);
if ($r !== TRUE)
break;
$a['type'] = $type;
$a['now'] = time();
if ($a['right_id'] > 0)
$sql = "UPDATE rights SET"
. " type = @@type@@"
. ", uid = @@uid@@"
. ", obj_id = @@obj_id@@"
. ", rights = @@rights@@"
. ", misc = @@misc@@"
. ", ip = @@ip@@"
. ", prio = @@prio@@"
. ", itime = @@now@@"
. ", who = @@who@@"
. ", description = @@description@@"
. " WHERE right_id = @@right_id@@";
else
$sql = "INSERT INTO rights (type, uid, obj_id, rights"
. ", misc, ip, prio, itime, who, description)"
. " VALUES (@@type@@, @@uid@@, @@obj_id@@, @@rights@@"
. ", @@misc@@, @@ip@@, @@prio@@, @@now@@, @@who@@"
. ", @@description@@)";
$res = rg_sql_query_params($db, $sql, $a);
if ($res === FALSE) {
rg_rights_set_error("cannot alter rights");
break;
}
rg_sql_free_result($res);
// invalidate cache (TODO: optimize by inserting in list and reorder)
$key = "rights_by_obj_id::" . $a['obj_id'] . "::" . $a['type'];
rg_cache_unset($key, RG_SOCKET_NO_WAIT);
$ret = TRUE;
break;
}
rg_log_exit();
rg_prof_end("rights_set");
return $ret;
}
/*
* Filters var using mask
* Example ("ABCDE", "AEZ") => "AE"
*/
function rg_rights_mask($val, $mask)
{
$ret = "";
$len = strlen($val);
for ($i = 0; $i < $len; $i++)
if (strstr($mask, $val[$i]) && !strstr($ret, $val[$i]))
$ret .= $val[$i];
return $ret;
}
/*
* Splits ip/prefix in components and apply the prefix len mask
* Returns FALSE if something is wrong
*/
function rg_rights_split_ip($ip)
{
$ret = array();
$ret['prefix_len'] = -1;
if (strstr($ip, "/")) { /* prefix len */
$t = explode("/", $ip);
$ip2 = $t[0];
$ret['prefix_len'] = $t[1];
} else {
$ip2 = $ip;
}
$ip2 = rg_fix_ip($ip2);
if (preg_match('/^[a-fA-F0-9:]*$/D', $ip2) === 1) { /* ipv6 */
if ($ret['prefix_len'] == -1) {
$ret['prefix_len'] = 128;
} else if (($ret['prefix_len'] < 0) || ($ret['prefix_len'] > 128)) {
rg_rights_set_error("invalid prefix len for [$ip]");
return FALSE;
}
$t = explode("::", $ip2);
if (count($t) > 2) {
rg_rights_set_error("invalid IPv6 IP [$ip] (multiple ::)");
return FALSE;
}
if (count($t) == 2) { /* we have :: */
$ipv6 = array();
/* count non-empty groups ($good) */
$t = explode(":", $ip2);
$good = 0;
foreach ($t as $p) {
if (!empty($p))
$good++;
}
$i = 0;
$fill = 1;
foreach ($t as $p) {
if (!empty($p)) {
$ipv6[$i++] = hexdec($p);
continue;
}
if ($fill == 0)
continue;
for ($j = 0; $j < 8 - $good; $j++)
$ipv6[$i++] = 0;
$fill = 0;
}
} else {
$ipv6 = explode(":", $ip2);
if (count($ipv6) != 8) {
rg_rights_set_error("invalid IPv6 IP [$ip]");
return FALSE;
}
foreach ($ipv6 as $k => $p)
$ipv6[$k] = hexdec($p);
}
// apply mask
for ($i = 0; $i < 8; $i++) {
if ($ret['prefix_len'] >= ($i + 1) * 16)
continue;
$len = ($i + 1) * 16 - $ret['prefix_len'];
if ($len >= 16) {
$ipv6[$i] = 0;
} else {
$mask = 0xFFFF - (pow(2, $len) - 1);
$ipv6[$i] &= $mask;
}
}
$new = array();
foreach ($ipv6 as $k => $p)
$new[$k] = sprintf("%x", $p);
$ret['ip'] = implode(":", $new);
$ret['type'] = "ipv6";
} else if (preg_match('/^[0-9\.]*$/D', $ip2) === 1) { /* ipv4 */
if ($ret['prefix_len'] == -1) {
$ret['prefix_len'] = 32;
} else if (($ret['prefix_len'] < 0) || ($ret['prefix_len'] > 32)) {
rg_rights_set_error("invalid prefix len for [$ip]");
return FALSE;
}
$ipv4 = explode(".", $ip2);
if (count($ipv4) != 4) {
rg_rights_set_error("invalid IPv4 IP [$ip]");
return FALSE;
}
foreach ($ipv4 as $k => $p) {
if (($p < 0) || ($p > 255)) {
rg_rights_set_error("invalid IPv4 IP [$ip]");
return FALSE;
}
$ipv4[$k] = ltrim($p, "0");
}
// apply mask
for ($i = 0; $i < 4; $i++) {
if ($ret['prefix_len'] >= ($i + 1) * 8)
continue;
$len = ($i + 1) * 8 - $ret['prefix_len'];
if ($len >= 8) {
$ipv4[$i] = "0";
} else {
$ipv4[$i] &= 0xFF - (pow(2, $len) - 1);
}
}
$ret['ip'] = implode(".", $ipv4);
$ret['type'] = "ipv4";
} else {
rg_rights_set_error("invalid address [$ip]");
return FALSE;
}
return $ret;
}
/*
* Validates a list of IPs to be correct
*/
function rg_rights_validate_ip($list)
{
$list = preg_replace("/[,\n]/", " ", $list);
$list = trim($list);
if (empty($list))
return TRUE;
$list = explode(" ", $list);
foreach ($list as $junk => $ip) {
if (empty($ip))
continue;
$r = rg_rights_split_ip($ip);
if ($r === FALSE)
return FALSE;
}
return TRUE;
}
/*
* Test if an IP match the allowed list
*/
function rg_rights_test_ip($list, $ip)
{
$r = rg_rights_split_ip($ip);
if ($r === FALSE) {
rg_log("An invalid IP has been specified [$ip]. Ignore it.");
return FALSE;
}
$list = explode(" ", $list);
$ret = FALSE;
foreach ($list as $junk => $ip0) {
if (empty($ip0)) {
$ret = TRUE;
break;
}
$r0 = rg_rights_split_ip($ip0);
if ($r0 === FALSE) {
rg_log("An invalid IP has been specified [$ip0]. Ignore it.");
continue;
}
$new_ip = rg_rights_split_ip($ip . "/" . $r0['prefix_len']);
if (strcmp($new_ip['type'], $r0['type']) != 0)
continue;
if (strcmp($new_ip['ip'], $r0['ip']) == 0) {
rg_log("$ip matches $ip0");
$ret = TRUE;
break;
}
rg_log("no match " . $new_ip['ip'] . " != " . $r0['ip']);
}
return $ret;
}
/*
* Returns TRUE if all 'needed_rights' are included in 'rights'
* @list - an array of rights
* @needed_rights: rights letters; you can use "ab|cd" = (a AND B) OR (C AND d)
*/
function rg_rights_test($list, $needed_rights, $ip, $misc)
{
global $rg_rights_cmp_func;
rg_log_enter("rights_test: needed_rights=$needed_rights ip=$ip"
. " misc=" . $misc);
//rg_log_ml('DEBUG list: ' . print_r($list, TRUE));
$ret = FALSE;
while (1) {
if (!is_array($list)) {
rg_rights_set_error("list is not array");
break;
}
if (empty($needed_rights)) {
$ret = TRUE;
break;
}
$needed = explode("|", $needed_rights);
foreach ($list as $k => $v) {
// Test IP
if (rg_rights_test_ip($v['ip'], $ip) !== TRUE) {
rg_log("CHECK: ip [$ip] does not match with [" . $v['ip'] . "]");
continue;
}
// Test 'misc' match
if (!empty($v['misc'])) {
$cmp_func = $rg_rights_cmp_func[$v['type']];
$r = $cmp_func($v['misc'], $misc);
if ($r !== TRUE) {
rg_log("DEBUG: cmp function returned !TRUE");
continue;
}
}
if (strlen($v['rights']) == 0) {
rg_log('DEBUG: rights field is empty'
. ', stop processing!');
break;
}
// Test rights
$have_a_match = FALSE;
foreach ($needed as $needed1) {
$r = rg_rights_mask($v['rights'], $needed1);
if (strcmp($r, $needed1) != 0) {
rg_log("[$r] != [$needed1]! Continue.");
continue;
}
rg_log("[$r] = [$needed1]! Allow.");
$have_a_match = TRUE;
break;
}
if ($have_a_match === FALSE)
continue;
rg_log('DEBUG: rule ' . $k . ' matched.');
$ret = TRUE;
break;
}
break;
}
rg_log("DEBUG: rights_test returns " . ($ret === FALSE ? "deny" : "allow"));
rg_log_exit();
return $ret;
}
/*
* Returns TRUE if all 'needed_rights' are included in 'rights'
* @a[needed_rights]: rights letters; you can use "ab|cd" = (a AND B) OR (C AND d)
*/
function rg_rights_allow($db, $a)
{
$obj_id = $a['obj_id'];
$type = $a['type'];
$owner = $a['owner'];
$uid = $a['uid'];
$username = $a['username'];
$needed_rights = $a['needed_rights'];
$ip = $a['ip'];
$misc = $a['misc'];
$right_id = 0;
// TODO: we may pass $a?
$r = rg_rights_get($db, $obj_id, $type, $owner, $uid, $right_id);
if ($r['ok'] != 1)
return FALSE;
// We must replace @USER@ with the logged-in user
if ($uid > 0) {
foreach ($r['list'] as $index => &$e) {
if (!strstr($e['misc'], '@USER@'))
continue;
$_old = $e['misc'];
$e['misc'] = str_replace('@USER@',
$username, $e['misc']);
rg_log("DEBUG [" . $_old . "] -> [" . $e['misc'] . "]");
}
//rg_log_ml("DEBUG: r[list]=" . print_r($r['list'], TRUE));
}
return rg_rights_test($r['list'], $needed_rights, $ip, $misc);
}
/*
* Delete a list of rights
* Caller must be sure that the user is allowed to operate on 'obj_id'.
*/
function rg_rights_delete_list($db, $type, $obj_id, $list)
{
rg_prof_start("rights_delete_list");
rg_log_enter("rights_delete_list: type=$type obj_id=$obj_id"
. " list=" . rg_array2string($list));
$ret = FALSE;
while (1) {
$db_list = implode(",", $list);
$params = array("obj_id" => $obj_id);
$sql = "DELETE FROM rights"
. " WHERE obj_id = @@obj_id@@"
. " AND right_id IN (" . $db_list . ")";
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
rg_rights_set_error("cannot mass delete (" . rg_sql_error() . ")!");
break;
}
// invalidate cache; TODO: this is the best way?
rg_cache_unset('rights_by_obj_id::' . $obj_id
. '::' . $type, RG_SOCKET_NO_WAIT);
break;
}
rg_log_exit();
rg_prof_end("rights_delete_list");
return TRUE;
}
?>