<?php require_once($INC . '/sql.inc.php'); require_once($INC . '/state.inc.php'); require_once($INC . '/prof.inc.php'); require_once($INC . '/cache.inc.php'); require_once($INC . '/plan.inc.php'); require_once($INC . '/ldap_core.inc.php'); require_once($INC . '/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_ml('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, NULL, 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'], NULL, 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_ml('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_ml('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! $ui['expire'] = gmmktime(0, 0, 0, 1, ($a['shadow_expire'] + 24 * 3600 - 1) / 24 / 3600, 1970) - 1; $ui['ok'] = 1; rg_log_ml('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_ml('DEBUG: uid=' . $uid); rg_log_ml('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') ); $r = rg_ldap_add($db, $rg['login_ui']['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; } ?>