<?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'] = ""; $row['HTML:description_nlbr'] = nl2br(rg_xss_safe($row['description'])); } /* * 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"); $ret = FALSE; while (1) { $params = array("type" => $type, "obj_id" => $obj_id); $sql = "SELECT * FROM rights" . " WHERE type = @@type@@" . " AND obj_id = @@obj_id@@" . " ORDER BY prio"; $res = rg_sql_query_params($db, $sql, $params); if ($res === FALSE) { rg_rights_set_error("cannot get info (" . rg_sql_error() . ")!"); break; } $ret = array(); while (($row = rg_sql_fetch_array($res))) { rg_rights_cosmetic($db, $row); $row['can_be_deleted'] = 1; $ret[] = $row; } rg_sql_free_result($res); break; } rg_log_exit(); rg_prof_end("rights_load"); return $ret; } /* * 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) { $key = "rights_by_obj_id::$obj_id::$type"; $r = rg_cache_get($key); if ($r === FALSE) { //rg_log("CHECK: rights_get: key not found in cache! Search in DB."); $r = array(); // 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)'; rg_rights_cosmetic($db, $a); $r[] = $a; //rg_log_ml("rights_get: inject all rights for owner"); } // 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_rights_cosmetic($db, $row); //rg_log_ml("rights_get: inject specific rights: " . print_r($row, TRUE)); $r[] = $row; } } $x = rg_rights_load($db, $obj_id, $type); if ($x === FALSE) break; $r = array_merge($r, $x); // We store the big list rg_cache_set($key, $r, RG_SOCKET_NO_WAIT); } else { //rg_log("CHECK: rights returned from cache for key $key"); } // now, filter by uid and right_id foreach ($r as $k => $v) { if (($right_id > 0) && ($v['right_id'] != $right_id)) continue; if (($uid == -1) || ($v['uid'] == $uid) || ($v['uid'] == 0)) $ret['list'][] = $v; } $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; } // Deal with ::ffff:46.102.28.148 if (strncasecmp($ip2, "::ffff:", 7) == 0) $ip2 = substr($ip2, 7); 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 was 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 was 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("rg_rights_test: needed_rights=$needed_rights ip=$ip" . " misc=$misc list:" . rg_array2string($list)); $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; $ret = TRUE; break; } break; } //rg_log("DEBUG: rights_test returns " . ($ret === FALSE ? "!allow" : "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; } ?>