<?php
require_once(__DIR__ . '/sql.inc.php');
require_once(__DIR__ . '/state.inc.php');
require_once(__DIR__ . '/prof.inc.php');
require_once(__DIR__ . '/cache.inc.php');
require_once(__DIR__ . '/plan.inc.php');
require_once(__DIR__ . '/ldap_core.inc.php');
require_once(__DIR__ . '/ldap_sync.inc.php');
$rg_ldap_error = '';
function rg_ldap_set_error($str)
{
global $rg_ldap_error;
$rg_ldap_error = $str;
rg_log($str);
}
function rg_ldap_error()
{
global $rg_ldap_error;
return $rg_ldap_error;
}
/*
* Some cosmetics applied to a LDAP server (one row)
*/
function rg_ldap_cosmetic_row($db, &$row)
{
if (isset($row['itime']))
$row['itime_nice'] = gmdate('Y-m-d H:i', $row['itime']);
$pi = rg_plan_info($db, $row['plan_id']);
if ($pi['exists'] == 1)
$row['plan'] = $pi['name'];
else if ($pi['ok'] == 1)
$row['plan'] = 'Plan ' . $row['plan_id'] . ' not found!';
else
$row['plan'] = rg_plan_error();
$row['who_nice'] = rg_user_nice($db, $row['who']);
}
/*
* Some cosmetics applied to a LDAP server (array)
*/
function rg_ldap_cosmetic_list($db, &$a)
{
foreach ($a as $k => &$row)
rg_ldap_cosmetic_row($db, $row);
}
/*
* Sorting the LDAP servers
*/
function rg_ldap_sort_helper($a, $b)
{
if ($a['prio'] < $b['prio'])
return -1;
if ($a['prio'] > $b['prio'])
return 1;
return strcmp($a['name'], $b['name']);
}
/*
* Returns a list of LDAP servers
*/
function rg_ldap_list($db)
{
rg_prof_start('ldap_list');
rg_log_enter('ldap_list');
$ret = array('ok' => 0, 'list' => array());
while (1) {
$key = 'ldap';
$r = rg_cache_get($key);
if (($r !== FALSE) && isset($r['LIST_LOADED'])) {
$ret['list'] = $r['list'];
$ret['ok'] = 1;
break;
}
$params = array();
$sql = 'SELECT * FROM ldap_servers ORDER BY prio, name';
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
rg_ldap_set_error('cannot load data');
break;
}
while (($row = rg_sql_fetch_array($res))) {
$id = $row['id'];
$ret['list'][$id] = $row;
}
rg_sql_free_result($res);
$a = array('LIST_LOADED' => 1, 'list' => $ret['list']);
rg_cache_merge($key, $a, RG_SOCKET_NO_WAIT);
$ret['ok'] = 1;
break;
}
if ($ret['ok'] == 1) {
rg_ldap_cosmetic_list($db, $ret['list']);
uasort($ret['list'], 'rg_ldap_sort_helper');
}
rg_log_exit();
rg_prof_end('ldap_list');
return $ret;
}
/*
* Adds/edits a ldap_server
*/
function rg_ldap_add($db, $who, $data)
{
rg_prof_start('ldap_add');
rg_log_enter('ldap_add');
$ret = array('ok' => 0);
while (1) {
$data['who'] = $who;
$data['itime'] = time();
$params = $data;
if ($data['id'] == 0) {
$sql = 'INSERT INTO ldap_servers (itime, who, name'
. ', url, bind_dn, bind_pass, user_base'
. ', uid_attr, filter, group_base, group_attr'
. ', group_filter, admin_group, ca_cert'
. ', prio, session_time, timeout, plan_id)'
. ' VALUES (@@itime@@, @@who@@, @@name@@'
. ', @@url@@, @@bind_dn@@, @@bind_pass@@'
. ', @@user_base@@, @@uid_attr@@, @@filter@@'
. ', @@group_base@@, @@group_attr@@'
. ', @@group_filter@@, @@admin_group@@'
. ', @@ca_cert@@, @@prio@@, @@session_time@@'
. ', @@timeout@@, @@plan_id@@)'
. ' RETURNING id';
} else {
$sql = 'UPDATE ldap_servers'
. ' SET itime = @@itime@@'
. ', who = @@who@@'
. ', name = @@name@@'
. ', url = @@url@@'
. ', bind_dn = @@bind_dn@@'
. ', bind_pass = @@bind_pass@@'
. ', user_base = @@user_base@@'
. ', uid_attr = @@uid_attr@@'
. ', filter = @@filter@@'
. ', group_base = @@group_base@@'
. ', group_attr = @@group_attr@@'
. ', group_filter = @@group_filter@@'
. ', admin_group = @@admin_group@@'
. ', ca_cert = @@ca_cert@@'
. ', prio = @@prio@@'
. ', session_time = @@session_time@@'
. ', timeout = @@timeout@@'
. ', plan_id = @@plan_id@@'
. ' WHERE id = @@id@@';
}
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
rg_ldap_set_error('cannot insert/update data');
break;
}
if ($data['id'] == 0)
$row = rg_sql_fetch_array($res);
rg_sql_free_result($res);
if ($data['id'] == 0)
$data['id'] = $row['id'];
$key = 'ldap' . '::' . 'list'
. '::' . $data['id'];
rg_cache_merge($key, $data, RG_SOCKET_NO_WAIT);
$ret['ok'] = 1;
break;
}
rg_log_exit();
rg_prof_end('ldap_add');
return $ret;
}
/*
* Removes a list of ldap_servers
*/
function rg_ldap_remove($db, $list)
{
rg_prof_start('ldap_remove');
rg_log_enter('ldap_remove');
$ret = array('ok' => 0);
while (1) {
if (empty($list)) {
rg_ldap_set_error('you did not select anything');
break;
}
$my_list = array();
foreach ($list as $id => $junk)
$my_list[] = sprintf('%u', $id);
$params = array();
$sql_list = implode(', ', $my_list);
$sql = 'DELETE FROM ldap_cache'
. ' WHERE server_id IN (' . $sql_list . ')';
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
rg_ldap_set_error('cannot remove data from db cache');
break;
}
rg_sql_free_result($res);
$sql = 'DELETE FROM ldap_servers'
. ' WHERE id IN (' . $sql_list . ')';
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
rg_ldap_set_error('cannot remove server from db');
break;
}
rg_sql_free_result($res);
foreach ($my_list as $junk => $id) {
$key = 'ldap' . '::' . 'list' . '::' . $id;
rg_cache_unset($key, RG_SOCKET_NO_WAIT);
}
$ret['ok'] = 1;
break;
}
rg_log_exit();
rg_prof_end('ldap_remove');
return $ret;
}
/*
* Helper function for ldap_login
*/
function rg_ldap_a_and_si_to_a_ui(&$ui, &$a, $si)
{
$a['username'] = $a['ldap_uid'];
$a['realname'] = $a['cn'] . ' (' . $a['gn'] . ' ' . $a['sn'] . ')';
$ui['session_time'] = $si['session_time'];
$ui['plan_id'] = $si['plan_id'];
$ui['realname'] = $a['realname'];
$ui['username'] = $a['username'];
$ui['email'] = $a['mail'];
$ui['pass'] = $a['password'];
$ui['pass2'] = $a['password'];
$ui['confirm_token'] = '';
$ui['confirmed'] = 1;
$ui['exists'] = 1;
$ui['deleted'] = 0;
$ui['suspended'] = 0;
}
/*
* Authentication function used by rg_user_login_by_user_pass
*/
rg_register_login_function(
array(
'login' => 'rg_ldap_login',
'post_login' => 'rg_ldap_login_post'
)
);
function rg_ldap_login($db, $user, $pass, &$ui)
{
global $rg_session_time;
rg_log_enter('ldap_login');
$ret = array();
$ret['ok'] = 0;
$found = FALSE;
while (1) {
$sl = rg_ldap_list($db);
if ($sl['ok'] !== 1) {
$ret['errmsg'] = $sl['errmsg'];
break;
}
if (empty($sl['list']))
break;
// First, we try to find the user in cache
// TODO: make the cache to expire
$r = rg_ldap_sync_get_cache($db, $user);
if ($r['ok'] === 1) {
foreach ($r['list'] as $a) {
rg_log_debug('cache: ' . print_r($a, TRUE));
// TODO: test if the entry is expired
// TODO: encrypt password!
if (strcmp($a['password'], $pass) != 0) {
rg_log_debug('passwords do not match ['
. $a['password'] . '] [' . $pass . ']');
continue;
}
$server_id = $a['server_id'];
if (!isset($sl['list'][$server_id])) {
rg_internal_error('We found a stall'
. ' ldap server_id in cache!');
continue;
}
rg_log_debug('Found a good cache entry!');
rg_ldap_a_and_si_to_a_ui($ui, $a,
$sl['list'][$server_id]);
if ($a['uid'] > 0)
$ui['uid'] = $a['uid'];
$ret['post'] = $a;
$ret['ok'] = 1;
break;
}
if ($ret['ok'] == 1)
break;
}
$euser = ldap_escape($user, '', LDAP_ESCAPE_FILTER);
foreach ($sl['list'] as $si) {
//rg_log_ml('ldap server info: ' . print_r($si, TRUE));
$r = rg_ldap_core_connect($si['url'], $si['timeout']);
if ($r['ok'] !== 1) {
rg_log_debug('cannot connect: ' . $r['errmsg']);
$ret['errmsg'] = $r['errmsg'];
continue;
}
$con = $r['con'];
rg_log_debug('connected to ' . $si['url']);
rg_log_debug('binding as [' . $si['bind_dn'] . ']');
$r = rg_ldap_core_bind($con, $si['bind_dn'],
$si['bind_pass']);
if ($r['ok'] !== 1) {
rg_log_debug('cannot bind: ' . $r['errmsg']);
$ret['errmsg'] = $r['errmsg'];
continue;
}
rg_log_debug('bind1 ok!');
// TODO: should I validate uid field - injection?
$uid_attr = strtolower($si['uid_attr']);
$filter = '(|'
. '(mail=' . $euser . ')'
. '(cn=' . $euser . ')'
. '(' . $uid_attr . '=' . $euser . ')'
. ')';
if (!empty($si['filter']))
$filter = '(&' . $filter
. '(' . ldap_escape($si['filter'], '', LDAP_ESCAPE_FILTER) . '))';
rg_log_debug('filter: ' . $filter);
rg_log_debug('base=' . $si['user_base']);
$deref = LDAP_DEREF_NEVER; // TODO: this should be in $si
$attr = array('cn', 'mail', 'entryUUID', 'memberOf',
'mail', 'sn', 'givenName', 'objectClass',
'uid', 'shadowExpire', 'uidNumber', 'gidNumber',
$uid_attr);
$r = rg_ldap_core_search($con, $si['user_base'], $filter,
$attr, 0 /*attronly*/, 0 /*sizelimit*/,
0 /*timelimit*/, $deref);
if ($r['ok'] !== 1) {
rg_log_debug('cannot search: ' . $r['errmsg']);
$ret['errmsg'] = $r['errmsg'];
continue;
}
if (empty($r['data']) || !isset($r['data'][0])) {
rg_log_debug('cannot find data');
$ret['errmsg'] = 'user not found';
continue;
}
// Have to test here if we can bind with the user found
// We may have more users, but we will select the first one
$d = $r['data'][0];
rg_log_debug('binding as [' . $d['dn'] . '] pass=' . $pass);
$r = rg_ldap_core_bind($con, $d['dn'], $pass);
if ($r['ok'] !== 1) {
rg_log_debug('cannot bind: ' . $r['errmsg']);
$ret['errmsg'] = $r['errmsg'];
continue;
}
rg_log_debug('bind2 ok!');
unset($ret['errmsg']);
$found = TRUE;
break;
}
if (!$found)
break;
rg_log_debug('got data from LDAP: d=' . print_r($d, TRUE));
// $a will be the $ret['post']
// It will be used to test if an update into 'users' is needed.
// Also, to insert into ldap_cache.
$a = array();
$a['cn'] = isset($d['cn'][0]) ? $d['cn'][0] : '';
$a['gn'] = isset($d['givenname'][0]) ? $d['givenname'][0] : '';
$a['sn'] = isset($d['sn'][0]) ? $d['sn'][0] : '';
$a['shadow_expire'] = isset($d['shadowexpire'][0]) ? $d['mail'][0] : '99999';
$uid_attr = strtolower($si['uid_attr']);
$a['ldap_uid'] = isset($d[$uid_attr][0]) ? $d[$uid_attr][0] : '';
$a['mail'] = isset($d['mail'][0]) ? $d['mail'][0] : '';
// TODO: really needed? I think not, we will use it in ldap_cache
//$a['uid_number'] = isset($d['uidnumber'][0]) ? $d['mail'][0] : '';
$a['password'] = $pass;
rg_log_debug('built a=' . print_r($a, TRUE));
rg_ldap_a_and_si_to_a_ui($ui, $a, $si);
// TODO: what to do when the admin changes the plan_id per server?
// I have to identify the users and change the plan.
$ui['is_admin'] = 0;
if (isset($d['memberof'])) {
for ($j = 0; $j < $d['memberof']['count']; $j++) {
//rg_log_debug('comparing ' . $d['memberof'][$j] . ' with ' . $si['admin_group']);
// TODO: Do I have to escape `?
$r = @preg_match('`' . $si['admin_group'] . '`uD', $d['memberof'][$j]);
if ($r === 1) {
$ui['is_admin'] = 1;
break;
}
}
}
$ui['suspended'] = $a['shadow_expire'] <= time() / 24 / 3600 ? 1 : 0;
// TODO: With 99999 it will not compute right!
$day = ($a['shadow_expire'] + 24 * 3600 - 1) / 24 / 3600;
$ui['expire'] = gmmktime(0, 0, 0, 1, intval($day), 1970) - 1;
$ui['ok'] = 1;
rg_log_debug('ui: ' . print_r($ui, TRUE));
// Prepare these for 'login_post' function, to update the cache.
$ret['post'] = $a;
$ret['post']['uid'] = 0;
$ret['post']['password'] = $pass;
$ret['post']['gid'] = isset($d['gidnumber'][0]) ? $d['gidnumber'][0] : 0;
$ret['post']['mail'] = $a['mail'];
$ret['post']['server_id'] = $si['id'];
$ret['post']['uuid'] = isset($d['entryuuid'][0]) ? $d['entryuuid'][0] : '';
$ret['ok'] = 1;
break;
}
rg_log_exit();
return $ret;
}
/*
* This is called from rg_user_login_by_user_pass.
* It will update the ldap_cache table.
* @post: it is the 'post' array member returned by rg_ldap_login
*/
function rg_ldap_login_post($db, $uid, $post)
{
rg_log_enter('ldap_login_post');
rg_log_debug('uid=' . $uid);
rg_log_debug('post: ' . print_r($post, TRUE));
$ret = array('ok' => 0);
while (1) {
if ($post['uid'] != $uid) {
rg_log_debug('we need to update ldap_cache.uid');
$post['uid'] = $uid;
$r = rg_ldap_sync_update_cache($db, $post);
if ($r['ok'] != 1) {
$ret['errmsg'] = $r['errmsg'];
break;
}
}
$ret['ok'] = 1;
break;
}
rg_log_exit();
return $ret;
}
/*
* High level function to list the LDAP servers
*/
function rg_ldap_list_high_level($db, $rg, $paras)
{
rg_prof_start('ldap_list_high_level');
rg_log_enter('ldap_list_high_level');
$ret = '';
$errmsg = array();
$delete = rg_var_uint('delete');
while ($delete == 1) {
if (!rg_valid_referer()) {
$errmsg[] = 'invalid referer; try again';
break;
}
if (!rg_token_valid($db, $rg, 'ldap_list', FALSE)) {
$errmsg[] = 'invalid token; try again.';
break;
}
$list = rg_var_str('delete_list');
$r = rg_ldap_remove($db, $list);
if ($r['ok'] !== 1) {
$errmsg[] = 'cannot delete: ' . rg_ldap_error();
break;
}
$ret .= rg_template('admin/ldap/delete_ok.html',
$rg, TRUE /*xss*/);
break;
}
if (!empty($errmsg)) {
$rg['HTML:errmsg'] = rg_template_errmsg($errmsg);
$ret .= rg_template('admin/ldap/delete_err.html',
$rg, TRUE /*xss*/);
}
$r = rg_ldap_list($db);
if ($r['ok'] !== 1) {
$rg['errmsg'] = rg_ldap_error();
$ret .= rg_template('admin/ldap/list_err.html',
$rg, TRUE /*xss*/);
} else {
rg_ldap_cosmetic_list($db, $r['list']);
$rg['rg_form_token'] = rg_token_get($db, $rg, 'ldap_list');
$ret .= rg_template_table('admin/ldap/list', $r['list'], $rg);
}
rg_log_exit();
rg_prof_end('ldap_list_high_level');
return $ret;
}
/*
* High level function to add/edit a LDAP server
*/
function rg_ldap_add_high_level($db, $rg, $op, $paras)
{
rg_prof_start('ldap_add_high_level');
rg_log_enter('ldap_add_high_level op=' . $op);
rg_log_debug('paras:' . rg_array2string($paras));
$ret = '';
$errmsg = array();
$show_form = TRUE;
$rg['ldap'] = array();
if (strcmp($op, 'add') == 0) {
$rg['ldap']['id'] = 0;
} else { // edit
if (isset($paras[0]))
$rg['ldap']['id'] = intval($paras[0]);
else
$rg['ldap']['id'] = 0;
}
$doit = rg_var_uint('doit');
while ($doit == 1) {
if (!rg_valid_referer()) {
$errmsg[] = 'invalid referer; try again';
break;
}
if (!rg_token_valid($db, $rg, 'ldap_add', FALSE)) {
$errmsg[] = 'invalid token; try again.';
break;
}
$rg['ldap'] = array(
'id' => rg_var_uint('ldap::id'),
'name' => rg_var_str('ldap::name'),
'plan_id' => rg_var_uint('ldap::plan_id'),
'prio' => rg_var_uint('ldap::prio'),
'session_time' => rg_var_uint('ldap::session_time'),
'url' => rg_var_str('ldap::url'),
'bind_dn' => rg_var_str('ldap::bind_dn'),
'bind_pass' => rg_var_str('ldap::bind_pass'),
'user_base' => rg_var_str('ldap::user_base'),
'uid_attr' => rg_var_str('ldap::uid_attr'),
'filter' => rg_var_str('ldap::filter'),
'group_base' => rg_var_str('ldap::group_base'),
'group_attr' => rg_var_str('ldap::group_attr'),
'group_filter' => rg_var_str('ldap::group_filter'),
'admin_group' => rg_var_str('ldap::admin_group'),
'timeout' => rg_var_uint('ldap::timeout'),
'ca_cert' => rg_var_str('ldap::ca_cert')
);
$ui_login = rg_ui_login();
$r = rg_ldap_add($db, $ui_login['uid'], $rg['ldap']);
if ($r['ok'] !== 1) {
$errmsg[] = rg_ldap_error();
break;
}
$ret .= rg_template('admin/ldap/edit_ok.html', $rg, TRUE /*xss*/);
$show_form = FALSE;
break;
}
$hints = array();
if ($show_form) {
if ($doit == 0) {
// Loading defaults values
if (strcmp($op, 'add') == 0) {
$rg['ldap'] = array(
'id' => 0,
'name' => '',
'plan_id' => 0,
'prio' => 0,
'session_time' => 3600,
'url' => '',
'bind_dn' => '',
'bind_pass' => '',
'user_base' => '',
'uid_attr' => 'uid',
'filter' => '',
'group_base' => '',
'group_attr' => '',
'group_filter' => '',
'admin_group' => '',
'timeout' => '10',
'ca_cert' => ''
);
} else { // edit
$_id = $rg['ldap']['id'];
$r = rg_ldap_list($db);
if ($r['ok'] != 1) {
$errmsg[] = 'cannot load info; try again later';
} else if (!isset($r['list'][$_id])) {
$errmsg[] = 'invalid id';
} else {
$rg['ldap'] = $r['list'][$_id];
}
}
}
$rg['HTML:select_plan'] = rg_plan_select($db, 'ldap::plan-id',
$rg['ldap']['plan_id']);
$hints[]['HTML:hint'] = rg_template(
'admin/ldap/hints.html', $rg, TRUE /*xss*/);
$rg['HTML:errmsg'] = rg_template_errmsg($errmsg);
$rg['rg_form_token'] = rg_token_get($db, $rg, 'ldap_add');
$ret .= rg_template('admin/ldap/add_edit.html',
$rg, TRUE /*xss*/);
} else {
$hints[]['HTML:hint'] = rg_template(
'admin/ldap/hints.html', $rg, TRUE /*xss*/);
}
$ret .= rg_template_table('hints/list', $hints, $rg);
rg_log_exit();
rg_prof_end('ldap_add_high_level');
return $ret;
}
/*
* Main HL function for LDAP
* (Admin -> LDAP)
*/
function rg_ldap_high_level($db, &$rg, $paras)
{
rg_prof_start('ldap_high_level');
rg_log_enter('ldap_high_level');
$ret = '';
while (1) {
$op = empty($paras) ? 'list' : array_shift($paras);
$rg['menu']['ldap'][$op] = 1;
$rg['HTML:menu_level2'] =
rg_template('admin/ldap/menu.html', $rg, TRUE /*xss*/);
switch ($op) {
case 'add':
case 'edit':
$ret .= rg_ldap_add_high_level($db, $rg, $op, $paras);
break;
default:
$ret .= rg_ldap_list_high_level($db, $rg, $paras);
break;
}
break;
}
rg_log_exit();
rg_prof_end('ldap_high_level');
return $ret;
}