<?php
require_once(__DIR__ . '/sql.inc.php');
require_once(__DIR__ . '/state.inc.php');
require_once(__DIR__ . '/prof.inc.php');
require_once(__DIR__ . '/ldap_core.inc.php');
/*
* Get data from cache
*/
function rg_ldap_sync_get_cache($db, $v)
{
rg_log_enter('ldap_sync_get_cache');
$ret = array('ok' => 0);
while (1) {
$params = array('v' => $v);
$sql = 'SELECT a.* FROM ldap_cache AS a, ldap_servers AS b'
. ' WHERE a.server_id = b.id'
. ' AND (mail = @@v@@'
. ' OR ldap_uid = @@v@@'
. ' OR cn = @@v@@)'
. ' ORDER BY b.prio';
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
$ret['errmsg'] = 'cannot select from cache';
break;
}
$ret['list'] = array();
while (($row = rg_sql_fetch_array($res)))
$ret['list'][] = $row;
rg_sql_free_result($res);
$ret['ok'] = 1;
break;
}
rg_log_exit();
return $ret;
}
/*
* Updates ldap_cache table
* TODO: should be moved to ldap.inc.php because is called from there?
* And get rid of "_sync_" in name.
*/
function rg_ldap_sync_update_cache($db, $l)
{
rg_log_enter('ldap_sync_update_cache');
rg_log_debug('l: ' . print_r($l, TRUE));
$ret = array('ok' => 0);
while (1) {
// We update uid only if != 0
$add = '';
if ($l['uid'] > 0)
$add .= ', uid = @@uid@@';
$sql = 'UPDATE ldap_cache SET'
. ' ldap_uid = @@ldap_uid@@'
. $add
. ', password = @@password@@'
. ', sn = @@sn@@'
. ', gn = @@gn@@'
. ', gid = @@gid@@'
. ', mail = @@mail@@'
. ', cn = @@cn@@'
. ', shadow_expire = @@shadow_expire@@'
. ' WHERE server_id = @@server_id@@'
. ' AND uuid = @@uuid@@';
$res = rg_sql_query_params($db, $sql, $l);
if ($res === FALSE) {
$ret['errmsg'] = 'error in update';
break;
}
$arows = rg_sql_affected_rows($res);
rg_sql_free_result($res);
if ($arows > 0) {
$ret['ok'] = 1;
break;
}
$sql = 'INSERT INTO ldap_cache (uid, ldap_uid, password'
. ', sn, gn, gid, mail, cn, shadow_expire'
. ', server_id, uuid)'
. ' VALUES (@@uid@@, @@ldap_uid@@, @@password@@'
. ', @@sn@@, @@gn@@, @@gid@@, @@mail@@'
. ', @@cn@@, @@shadow_expire@@, @@server_id@@'
. ', @@uuid@@)';
$ignore = array(RG_SQL_UNIQUE_VIOLATION);
$res = rg_sql_query_params_ignore($db, $sql, $l,
$ignore, $ignore_kicked);
if ($res === FALSE) {
if (!$ignore_kicked) {
$ret['errmsg'] = 'error in update';
break;
}
rg_log('Entry already in cache.');
} else {
rg_sql_free_result($res);
}
$ret['ok'] = 1;
break;
}
rg_log_exit();
return $ret;
}
/*
* Updates ldap_servers table - for now, CSNs
* TODO: It should be used to set also the last error message?
* Maybe we should have a different log for this kind of problems,
* and send it to the admin.
*/
function rg_ldap_sync_update_server($db, $server_id, $csn)
{
rg_log_enter('ldap_sync_update_server');
$ret = array('ok' => 0);
while (1) {
if (empty($csn)) {
$ret['ok'] = 1;
break;
}
$params = array(
'server_id' => $server_id,
'csn' => $csn
);
$sql = 'UPDATE ldap_servers SET'
. ' csn = @@csn@@'
. ' WHERE id = @@server_id@@';
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
$ret['errmsg'] = 'error in update';
break;
}
rg_sql_free_result($res);
$ret['ok'] = 1;
break;
}
rg_log_exit();
return $ret;
}
/*
* It applies the updates to the database.
* Input is a ldif parsed as an array (from rg_ldap_core_ldif2array)
*/
function rg_ldap_sync_apply($db, $server_id, $data)
{
rg_log_enter('ldap_sync_apply');
$ret = array();
$ret['ok'] = 0;
while (1) {
rg_log_ml('data: ' . print_r($data, TRUE));
if (rg_sql_begin($db) !== TRUE) {
$ret['errmsg'] = 'cannot start transaction';
break;
}
$rollback = TRUE;
$csn = array();
foreach ($data as $block_id => $block) {
if (isset($block['entryCSN'][0])) {
rg_log_debug('entryCSN is present!');
$c = $block['entryCSN'][0];
// Example: 20171001075924.155248Z#000000#001#000000
$_t = explode('#', $c);
$_s = isset($_t[2]) ? $_t[2] : '';
if (!isset($csn[$_s]) || ($c > $csn[$_s]))
$csn[$_s] = $c;
} else {
rg_log_debug('entryCSN is NOT present!');
}
if (!isset($block['objectClass'])) {
$ret['errmsg'] = 'missing objectClass';
break;
}
// Try to detect if is a user entry
$is_user = FALSE;
foreach ($block['objectClass'] as $oc) {
if (strcasecmp($oc, 'inetOrgPerson') == 0) {
$is_user = TRUE;
break;
}
}
if (!$is_user)
continue;
$l = array();
$l['uid'] = 0;
$l['ldap_uid'] = isset($block['uid'][0]) ? $block['uid'][0] : '';
$l['password'] = isset($block['userPassword'][0]) ? $block['userPassword'][0] : '';
$l['sn'] = isset($block['sn'][0]) ? $block['sn'][0] : '';
$l['gn'] = isset($block['givenName'][0]) ? $block['givenName'][0] : '';
$l['gid'] = isset($block['gidNumber'][0]) ? $block['gidNumber'][0] : 0;
$l['mail'] = isset($block['mail'][0]) ? $block['mail'][0] : '';
$l['cn'] = isset($block['cn'][0]) ? $block['cn'][0] : '';
$l['shadow_expire'] = isset($block['shadowExpire'][0]) ? $block['shadowExpire'][0] : '99999';
$l['server_id'] = $server_id;
$l['uuid'] = isset($block['entryUUID'][0]) ? $block['entryUUID'][0] : '';
// Verifications
if (empty($l['ldap_uid'])) {
rg_log_debug('ldap_uid is not present!');
continue;
}
if (empty($l['uuid'])) {
rg_log_debug('entryUUID is not present!');
continue;
}
rg_log_debug('l:' . print_r($l, TRUE));
$r = rg_ldap_sync_update_cache($db, $l);
if ($r['ok'] !== 1) {
$r['errmsg'] = $r['errmsg'];
break;
}
/*
TODO foreach ($block['memberOf'] as $m)
$pairs_groups[] = array($uuid, $m);
*/
}
if (isset($ret['errmsg']))
break;
rg_log_debug('csn: ' . print_r($csn, TRUE));
$r = rg_ldap_sync_update_server($db, $server_id,
implode(';', $csn));
if ($r['ok'] !== 1) {
$ret['errmsg'] = $r['errmsg'];
break;
}
if (rg_sql_commit($db) !== TRUE) {
$ret['errmsg'] = 'cannot commit';
break;
}
$ret['ok'] = 1;
$rollback = FALSE;
break;
}
if ($rollback)
rg_sql_rollback($db);
rg_log_exit();
return $ret;
}
/*
* Function called to process raw ldif blocks -> database
*/
function rg_ldap_sync_data($db, $server_id, $s)
{
rg_log_enter('ldap_sync_data');
rg_log_debug('s=' . $s);
$ret = array();
$ret['ok'] = 0;
while (1) {
$a = rg_ldap_core_ldif2array($s);
if ($a['ok'] != 1) {
$ret['errmsg'] = $a['errmsg'];
break;
}
// Update database
if (!empty($a['data'])) {
$r = rg_ldap_sync_apply($db, $server_id, $a['data']);
if ($r['ok'] != 1) {
$ret['errmsg'] = $r['errmsg'];
break;
}
}
$ret['ok'] = 1;
$ret['used'] = $a['used'];
break;
}
rg_log_exit();
return $ret;
}
/*
* 'input' callback for rg_ldap_sync_* functions
*/
function rg_ldap_sync_cb_input($index, &$info, $stream)
{
rg_log_enter('ldap_sync_cb_input: index=' . $index
. ' stream=' . $stream);
while (1) {
//rg_log_ml('info: ' . print_r($info, TRUE));
$c = &$info['custom'];
switch ($stream) {
case 1:
//rg_log('Got data: ' . $info['in_buf']);
$r = rg_ldap_sync_data($c['db'], $c['server_id'],
$info['in_buf']);
if ($r['ok'] != 1) {
rg_log('Error: ' . $r['errmsg']);
$info['done'] = TRUE;
break;
}
$info['in_buf'] = substr($info['in_buf'], $r['used']);
break;
case 2:
// TODO: should we do something with this?
// Maybe store it in the synchronization log?
// Send it do admin?
rg_log_ml('error received: ' . $info['err_buf']);
$info['err_buf'] = '';
break;
}
break;
}
rg_log_exit();
}
/*
* 'error' calback for rg_ldap_sync_* functions
*/
function rg_ldap_sync_cb_error($index, &$info, $msg)
{
rg_log('ldap_sync_cb_error: ' . $msg);
$info['done'] = TRUE;
}
/*
* Sync function using SyncRepl protocol (ro way)
*/
function rg_ldap_sync_ro($db, $data)
{
// TODO: move this in the higher level part
// Load the list of servers in decreasing priority order (increasing numeric)
// because we want the best one to overwrite the lower priority ones.
// Hm! Have a problem: sync_rp with multiple servers!
$ret = array();
$ret['ok'] = 0;
while (1) {
// Store password
$r = rg_id(16);
// TODO: choose a better name; choose a better dir
$pass_file = '/tmp/' . $r;
$f = @fopen($pass_file, 'w');
if ($f === FALSE) {
$ret['errmsg'] = 'cannot create pass file';
break;
}
if (@chmod($pass_file, 0600) !== TRUE) {
fclose($f);
$ret['errmsg'] = 'cannot chmod pass';
break;
}
$r = @fwrite($f, $data['bind_pass']);
if ($r === FALSE) {
fclose($f);
$ret['errmsg'] = 'cannot write pass';
break;
}
fclose($f);
$cmd = 'ldapsearch -d-1 -LLL -v -b ' . escapeshellarg($data['base'])
. ' -x -H ldap://' . escapeshellarg($data['addr'])
. ':' . escapeshellarg($data['port'])
. ' -D ' . escapeshellarg($data['bind_user'])
. ' -y ' . escapeshellarg($pass_file)
. ' -s sub -P 3';
if (isset($data['rid']) && isset($data['csn'])) {
$cmd .= ' -E ' . escapeshellarg('sync=ro/'
. 'rid=' . $data['rid']
. ',sid=001'
. ',csn=' . $data['csn']);
} else {
$cmd .= ' -E sync=ro/rid=001,sid=001,csn=aaa';
}
if (isset($data['fields'])) {
$fields = '';
$add = '';
foreach($data['fields'] as $f) {
$fields .= $add . $f;
$add = ',';
}
} else {
$fields = '*';
}
$cmd .= ' "(|'
. '(objectClass=groupOfNames)'
. '(objectClass=groupOfUniqueNames)'
. '(structuralObjectClass=inetOrgPerson)'
. ')"';
$cmd .= ' ' . escapeshellarg($fields) . ' +';
$a = array(
'cmds' => array(
'cmd1' => array(
'cmd' => $cmd,
'cb_input' => 'rg_ldap_sync_cb_input',
'cb_error' => 'rg_ldap_sync_cb_error',
'custom' => array(
'db' => $db,
'server_id' => $data['server_id']
)
)
)
);
$r = rg_exec2($a);
@unlink($pass_file);
if ($r['ok'] != 1) {
$ret['errmsg'] = $r['errmsg'];
break;
}
$ret['ok'] = 1;
break;
}
return $ret;
}