2014-11-05 21:27:52 +01:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* API - accounts Univention LDAP backend
|
|
|
|
*
|
|
|
|
* @link http://www.egroupware.org
|
|
|
|
* @author Ralf Becker <rb@stylite.de>
|
|
|
|
*
|
|
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
|
|
* @package api
|
|
|
|
* @subpackage accounts
|
|
|
|
*/
|
|
|
|
|
2016-03-06 16:54:07 +01:00
|
|
|
namespace EGroupware\Api\Accounts;
|
|
|
|
|
2016-03-08 09:13:26 +01:00
|
|
|
use EGroupware\Api;
|
|
|
|
|
2014-11-05 21:27:52 +01:00
|
|
|
/**
|
|
|
|
* Univention LDAP Backend for accounts
|
|
|
|
*
|
|
|
|
* This backend is mostly identical to LDAP backend and need to be configured in the same way.
|
2015-08-06 14:12:16 +02:00
|
|
|
*
|
2014-11-05 21:27:52 +01:00
|
|
|
* Only difference is that new users get created via univention-directory-manager CLI program,
|
|
|
|
* to generate necesary Kerberos stuff.
|
2015-08-06 14:12:16 +02:00
|
|
|
*
|
2018-07-03 22:23:53 +02:00
|
|
|
* New groups are generated via same CLI, if we have an ID/RID to set.
|
|
|
|
*
|
2015-08-06 14:12:16 +02:00
|
|
|
* Existing users and groups need to be renamed via same CLI, as removing and
|
|
|
|
* adding entry under new dn via LDAP fails (Type or value exists).
|
2014-11-05 21:27:52 +01:00
|
|
|
*/
|
2016-03-06 16:54:07 +01:00
|
|
|
class Univention extends Ldap
|
2014-11-05 21:27:52 +01:00
|
|
|
{
|
2015-02-14 13:55:05 +01:00
|
|
|
/**
|
|
|
|
* Attribute with mail address
|
|
|
|
*/
|
|
|
|
const MAIL_ATTR = 'mailprimaryaddress';
|
|
|
|
|
2014-11-05 21:27:52 +01:00
|
|
|
/**
|
|
|
|
* Name of binary to call
|
2015-07-23 16:56:20 +02:00
|
|
|
*
|
|
|
|
* It is a symlink to /usr/share/univention-directory-manager-tools/directory-manager-cli.
|
|
|
|
* Both directories must be included in open_basedir!
|
2014-11-05 21:27:52 +01:00
|
|
|
*/
|
|
|
|
const DIRECTORY_MANAGER_BIN = '/usr/sbin/univention-directory-manager';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Saves / adds the data of one account
|
|
|
|
*
|
|
|
|
* If no account_id is set in data the account is added and the new id is set in $data.
|
|
|
|
*
|
|
|
|
* @param array $data array with account-data
|
|
|
|
* @return int|boolean the account_id or false on error
|
|
|
|
*/
|
|
|
|
function save(&$data)
|
|
|
|
{
|
2015-09-18 09:18:58 +02:00
|
|
|
// UCS lowercases email when storing
|
|
|
|
$data['account_email'] = strtolower($data['account_email']);
|
|
|
|
|
2015-08-06 14:12:16 +02:00
|
|
|
if (self::available())
|
2014-11-05 21:27:52 +01:00
|
|
|
{
|
2018-07-03 22:23:53 +02:00
|
|
|
$ssh = null;//'/usr/bin/ssh -o "UserKnownHostsFile=/dev/null" -o "StrictHostKeyChecking=no" -i /var/lib/egroupware/id_rsa root@10.44.22.194';
|
2014-11-06 17:54:33 +01:00
|
|
|
$config = $this->frontend->config && $this->frontend->config['ldap_context'] ?
|
|
|
|
$this->frontend->config : $GLOBALS['egw_info']['server'];
|
2015-08-06 14:12:16 +02:00
|
|
|
|
2018-07-03 22:23:53 +02:00
|
|
|
if ($data['account_type'] !== 'g' && (empty($data['account_id']) || !$this->id2name($data['account_id'])))
|
2014-11-05 21:27:52 +01:00
|
|
|
{
|
2018-07-03 22:23:53 +02:00
|
|
|
// empty names give an error: The property Last/First name is required is not valid
|
|
|
|
if (empty($data['account_firstname'])) $data['account_firstname'] = 'n/a';
|
|
|
|
if (empty($data['account_lastname'])) $data['account_lastname'] = 'n/a';
|
|
|
|
|
2015-08-06 14:12:16 +02:00
|
|
|
$params = array(
|
|
|
|
'users/user','create',
|
|
|
|
'--binddn', $config['ldap_root_dn'],
|
|
|
|
'--bindpwd', 5=>$config['ldap_root_pw'],
|
|
|
|
'--position', $config['ldap_context'],
|
|
|
|
'--set', 'username='.$data['account_lid'],
|
|
|
|
'--set', 'firstname='.$data['account_firstname'],
|
|
|
|
'--set', 'lastname='.$data['account_lastname'],
|
|
|
|
);
|
2018-07-03 22:23:53 +02:00
|
|
|
|
|
|
|
// we can't create a new user without a password, setting a randowm one for now
|
|
|
|
$matches = null;
|
|
|
|
if (empty($data['account_passwd']) || preg_match('/^{([a-z0-9_]+)}/i', $data['account_passwd'], $matches))
|
2015-08-06 14:12:16 +02:00
|
|
|
{
|
2018-07-03 22:23:53 +02:00
|
|
|
if ($matches && strtolower($matches[1]) === 'plain')
|
|
|
|
{
|
|
|
|
$data['account_passwd'] = substr($data['account_passwd'], 7);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$data['account_passwd'] = Api\Auth::randomstring(12);
|
|
|
|
//file_put_contents('/tmp/passwords', "$data[account_lid]\t$data[account_passwd]\n", FILE_APPEND);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$params[] = '--set'; $params[] = 'password='.$data['account_passwd'];
|
|
|
|
|
|
|
|
// if account_id is given and bigger then 1000, set it to facilitate migration
|
|
|
|
if (!empty($data['account_id']) && $data['account_id'] >= Ads::MIN_ACCOUNT_ID)
|
|
|
|
{
|
|
|
|
$params[] = '--set'; $params[] = 'uidNumber='.(int)$data['account_id'];
|
|
|
|
$params[] = '--set'; $params[] = 'sambaRID='.(int)$data['account_id'];
|
2015-08-06 14:12:16 +02:00
|
|
|
}
|
2018-07-03 22:23:53 +02:00
|
|
|
|
|
|
|
if (!empty($data['account_email']))
|
2015-08-06 14:12:16 +02:00
|
|
|
{
|
2018-07-03 22:23:53 +02:00
|
|
|
$params[] = '--set'; $params[] = 'mailPrimaryAddress='.$data['account_email'];
|
|
|
|
|
2015-08-06 14:12:16 +02:00
|
|
|
// we need to set mailHomeServer, so mailbox gets created for Dovecot
|
|
|
|
// get_default() does not work for Adminstrator, try acc_id=1 instead
|
|
|
|
// if everything fails try hostname ...
|
|
|
|
try {
|
2016-03-28 20:51:38 +02:00
|
|
|
if (!($account = Api\Mail\Account::get_default(false, false, false)))
|
2015-08-06 14:12:16 +02:00
|
|
|
{
|
2016-03-28 20:51:38 +02:00
|
|
|
$account = Api\Mail\Account::read(1);
|
2015-08-06 14:12:16 +02:00
|
|
|
}
|
|
|
|
$hostname = $account->acc_imap_host;
|
|
|
|
}
|
2016-07-04 15:07:18 +02:00
|
|
|
catch(\Exception $e) {
|
2015-08-06 14:12:16 +02:00
|
|
|
unset($e);
|
2015-07-23 16:56:20 +02:00
|
|
|
}
|
2018-07-03 22:23:53 +02:00
|
|
|
//$hostname='master.test-org.intranet';
|
|
|
|
if (empty($hostname)) $hostname = trim(exec('hostname -f'));
|
2015-08-06 14:12:16 +02:00
|
|
|
$params[] = '--set'; $params[] = 'mailHomeServer='.$hostname;
|
2015-07-23 16:56:20 +02:00
|
|
|
}
|
2015-08-06 14:12:16 +02:00
|
|
|
$cmd = self::DIRECTORY_MANAGER_BIN.' '.implode(' ', array_map('escapeshellarg', $params));
|
2018-07-03 22:23:53 +02:00
|
|
|
if (isset($ssh)) $cmd = $ssh.' bash -c "\\"'.$cmd.'\\""';
|
2015-08-06 14:12:16 +02:00
|
|
|
$output_arr = $ret = $matches = null;
|
|
|
|
exec($cmd, $output_arr, $ret);
|
|
|
|
$output = implode("\n", $output_arr);
|
|
|
|
if ($ret || !preg_match('/^Object created: (uid=.*)$/mui', $output, $matches))
|
|
|
|
{
|
|
|
|
$params[5] = '********'; // mask out password!
|
|
|
|
$cmd = self::DIRECTORY_MANAGER_BIN.' '.implode(' ', array_map('escapeshellarg', $params));
|
2016-03-06 16:54:07 +01:00
|
|
|
throw new Api\Exception\WrongUserinput($cmd."\nreturned\n".$output);
|
2015-07-23 16:56:20 +02:00
|
|
|
}
|
2015-08-06 14:12:16 +02:00
|
|
|
$data['account_dn'] = $matches[1];
|
|
|
|
$data['account_id'] = $this->name2id($data['account_lid'], 'account_lid', 'u');
|
2014-11-05 21:27:52 +01:00
|
|
|
}
|
2018-07-03 22:23:53 +02:00
|
|
|
// create new groups with given account_id via directory-manager too, to be able to set the RID
|
|
|
|
elseif($data['account_type'] === 'g' && !empty($data['account_id']) &&
|
|
|
|
$data['account_id'] >= Ads::MIN_ACCOUNT_ID && !$this->id2name($data['account_id']))
|
|
|
|
{
|
|
|
|
$params = array(
|
|
|
|
'groups/group', 'create',
|
|
|
|
'--binddn', $config['ldap_root_dn'],
|
|
|
|
'--bindpwd', 5=>$config['ldap_root_pw'],
|
|
|
|
'--position', empty($config['ldap_group_context']) ? $config['ldap_context'] : $config['ldap_group_context'],
|
|
|
|
'--set', 'name='.$data['account_lid'],
|
|
|
|
'--set', 'gidNumber='.(int)$data['account_id'],
|
|
|
|
'--set', 'sambaRID='.(int)$data['account_id'],
|
|
|
|
);
|
|
|
|
|
|
|
|
$cmd = self::DIRECTORY_MANAGER_BIN.' '.implode(' ', array_map('escapeshellarg', $params));
|
|
|
|
if (isset($ssh)) $cmd = $ssh.' bash -c "\\"'.$cmd.'\\""';
|
|
|
|
$output_arr = $ret = $matches = null;
|
|
|
|
exec($cmd, $output_arr, $ret);
|
|
|
|
$output = implode("\n", $output_arr);
|
|
|
|
if ($ret || !preg_match('/^Object created: (cn=.*)$/mui', $output, $matches))
|
|
|
|
{
|
|
|
|
$params[5] = '********'; // mask out password!
|
|
|
|
$cmd = self::DIRECTORY_MANAGER_BIN.' '.implode(' ', array_map('escapeshellarg', $params));
|
|
|
|
throw new Api\Exception\WrongUserinput($cmd."\nreturned\n".$output);
|
|
|
|
}
|
|
|
|
$data['account_dn'] = $matches[1];
|
|
|
|
$data['account_id'] = $this->name2id($data['account_lid'], 'account_lid', 'g');
|
|
|
|
}
|
2015-08-06 14:12:16 +02:00
|
|
|
elseif($data['account_id'] && ($data['old_loginid'] || ($data['old_loginid'] = $this->id2name($data['account_id']))) &&
|
|
|
|
$data['account_lid'] != $data['old_loginid'] &&
|
|
|
|
($data['account_dn'] = $this->id2name($data['account_id'], 'account_dn')))
|
2014-11-05 21:27:52 +01:00
|
|
|
{
|
2015-08-06 14:12:16 +02:00
|
|
|
$params = array(
|
|
|
|
$data['account_type'] !== 'g' ? 'users/user' : 'groups/group', 'modify',
|
|
|
|
'--binddn', $config['ldap_root_dn'],
|
|
|
|
'--bindpwd', 5=>$config['ldap_root_pw'],
|
|
|
|
'--dn', $data['account_dn'],
|
|
|
|
'--set', ($data['account_type'] !== 'g' ? 'username' : 'name').'='.$data['account_lid'],
|
|
|
|
);
|
2014-11-05 21:27:52 +01:00
|
|
|
$cmd = self::DIRECTORY_MANAGER_BIN.' '.implode(' ', array_map('escapeshellarg', $params));
|
2018-07-03 22:23:53 +02:00
|
|
|
if (isset($ssh)) $cmd = $ssh.' bash -c "\\"'.$cmd.'\\""';
|
2015-08-06 14:12:16 +02:00
|
|
|
$output_arr = $ret = $matches = null;
|
|
|
|
exec($cmd, $output_arr, $ret);
|
|
|
|
$output = implode("\n", $output_arr);
|
|
|
|
if ($ret || !preg_match('/^Object modified: ((uid|cn)=.*)$/mui', $output, $matches))
|
|
|
|
{
|
|
|
|
$params[5] = '********'; // mask out password!
|
|
|
|
$cmd = self::DIRECTORY_MANAGER_BIN.' '.implode(' ', array_map('escapeshellarg', $params));
|
2016-03-06 16:54:07 +01:00
|
|
|
throw new Api\Exception\WrongUserinput($cmd."\nreturned\n".$output);
|
2015-08-06 14:12:16 +02:00
|
|
|
}
|
|
|
|
$data['account_dn'] = $data['account_type'] !== 'g' ? $matches[1] :
|
|
|
|
// duno why but directory-manager returns old dn for groups ...
|
|
|
|
preg_replace('/^cn=[^,]+,/', 'cn='.$data['account_lid'].',', $data['account_dn']);
|
2014-11-05 21:27:52 +01:00
|
|
|
}
|
|
|
|
}
|
2015-08-06 14:12:16 +02:00
|
|
|
//else error_log(__METHOD__."() ".self::DIRECTORY_MANAGER_BIN." is NOT available!");
|
2014-11-05 21:27:52 +01:00
|
|
|
return parent::save($data);
|
|
|
|
}
|
|
|
|
|
2018-07-03 22:23:53 +02:00
|
|
|
/**
|
|
|
|
* convert an alphanumeric account-value (account_lid, account_email) to the account_id
|
|
|
|
*
|
|
|
|
* Reimplement to check for users outside regular user-dn eg. functional users
|
|
|
|
*
|
|
|
|
* @param string $_name value to convert
|
|
|
|
* @param string $which ='account_lid' type of $name: account_lid (default), account_email, person_id, account_fullname
|
|
|
|
* @param string $account_type u = user, g = group, default null = try both
|
|
|
|
* @return int|false numeric account_id or false on error ($name not found)
|
|
|
|
*/
|
|
|
|
function name2id($_name,$which='account_lid',$account_type=null)
|
|
|
|
{
|
|
|
|
if ((!$id = parent::name2id($_name, $which, $account_type)))
|
|
|
|
{
|
|
|
|
$user_dn = $this->user_context;
|
|
|
|
$this->user_context = preg_replace('/(cn|uid)=([^,]+),/i', '', $this->user_context);
|
|
|
|
|
|
|
|
$id = parent::name2id($_name, $which, $account_type);
|
|
|
|
|
|
|
|
$this->user_context = $user_dn;
|
|
|
|
}
|
|
|
|
return $id;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert an numeric account_id to any other value of that account (account_lid, account_email, ...)
|
|
|
|
*
|
|
|
|
* Reimplement to check for users outside regular user-dn eg. functional users
|
|
|
|
*
|
|
|
|
* @param int $account_id numerica account_id
|
|
|
|
* @param string $which ='account_lid' type to convert to: account_lid (default), account_email, ...
|
|
|
|
* @return string|false converted value or false on error ($account_id not found)
|
|
|
|
*/
|
|
|
|
function id2name($account_id,$which='account_lid')
|
|
|
|
{
|
|
|
|
if (($name = parent::id2name($account_id, $which)) === false)
|
|
|
|
{
|
|
|
|
if (!is_numeric($account_id)) $account_id = $this->name2id($account_id);
|
|
|
|
|
|
|
|
$user_dn = $this->user_context;
|
|
|
|
$this->user_context = preg_replace('/(cn|uid)=([^,]+),/i', '', $this->user_context);
|
|
|
|
|
|
|
|
if ($account_id && ($data = $this->read($account_id)))
|
|
|
|
{
|
|
|
|
$name = $data[$which];
|
|
|
|
}
|
|
|
|
$this->user_context = $user_dn;
|
|
|
|
}
|
|
|
|
return $name;
|
|
|
|
}
|
|
|
|
|
2014-11-05 21:27:52 +01:00
|
|
|
/**
|
|
|
|
* Check if our function depending on an external binary is available
|
|
|
|
*
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
public static function available()
|
|
|
|
{
|
2018-07-03 22:23:53 +02:00
|
|
|
//return true;
|
2014-11-05 21:27:52 +01:00
|
|
|
return file_exists(self::DIRECTORY_MANAGER_BIN) && is_executable(self::DIRECTORY_MANAGER_BIN);
|
|
|
|
}
|
|
|
|
}
|