Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
115 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/Auth/LDAP.php b/lib/Auth/LDAP.php
index 7de9017..25cb754 100644
--- a/lib/Auth/LDAP.php
+++ b/lib/Auth/LDAP.php
@@ -1,1593 +1,1615 @@
<?php
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab Web Admin Panel |
| |
| Copyright (C) 2011-2012, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
| Author: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
require_once "Net/LDAP3.php";
/**
* Kolab LDAP handling abstraction class.
*/
class LDAP extends Net_LDAP3 {
private $conf;
/**
* Class constructor
*/
public function __construct($domain = null)
{
parent::__construct();
$this->conf = Conf::get_instance();
// Causes nesting levels to be too deep...?
//$this->config_set('config_get_hook', array($this, "_config_get"));
$this->config_set("debug", true);
$this->config_set("log_hook", array($this, "_log"));
$this->config_set("config_root_dn", "cn=config");
$this->config_set("service_bind_dn", $this->conf->get("service_bind_dn"));
$this->config_set("service_bind_pw", $this->conf->get("service_bind_pw"));
$this->config_set("login_filter", $this->conf->get("kolab_wap", "login_filter"));
$this->config_set("vlv", $this->conf->get("ldap", "vlv", Conf::AUTO));
// See if we are to connect to any domain explicitly defined.
if (empty($domain)) {
// If not, attempt to get the domain from the session.
if (isset($_SESSION['user'])) {
try {
$domain = $_SESSION['user']->get_domain();
} catch (Exception $e) {
Log::warning("LDAP: User not authenticated yet");
}
}
} else {
Log::debug("LDAP: __construct() using domain $domain");
}
// Continue and default to the primary domain.
$this->domain = $domain ? $domain : $this->conf->get('primary_domain');
$unique_attr = $this->conf->get($domain, 'unique_attribute');
if (empty($unique_attr)) {
$unique_attr = $this->conf->get('ldap', 'unique_attribute');
}
if (empty($unique_attr)) {
$unique_attr = 'nsuniqueid';
}
$this->config_set('unique_attribute', $unique_attr);
$this->_ldap_uri = $this->conf->get('ldap_uri');
$this->_ldap_server = parse_url($this->_ldap_uri, PHP_URL_HOST);
$this->_ldap_port = parse_url($this->_ldap_uri, PHP_URL_PORT);
$this->_ldap_scheme = parse_url($this->_ldap_uri, PHP_URL_SCHEME);
// Catch cases in which the ldap server port has not been explicitely defined
if (!$this->_ldap_port) {
if ($this->_ldap_scheme == "ldaps") {
$this->_ldap_port = 636;
}
else {
$this->_ldap_port = 389;
}
}
$this->config_set("host", $this->_ldap_server);
$this->config_set("port", $this->_ldap_port);
$this->config_set("use_tls", $this->_ldap_scheme == 'tls');
parent::connect();
// Attempt to get the root dn from the configuration file.
$root_dn = $this->conf->get($this->domain, "base_dn");
if (empty($root_dn)) {
// Fall back to a root dn from LDAP, or the standard root dn
$root_dn = $this->domain_root_dn($this->domain);
}
$this->config_set("root_dn", $root_dn);
}
/**********************************************************
*********** Public methods ************
**********************************************************/
/**
* Authentication
*
* @param string $username User name (DN or mail)
* @param string $password User password
*
* @return bool|string User ID or False on failure
*/
public function authenticate($username, $password, $domain = NULL)
{
Log::debug("Auth::LDAP: authentication request for $username against domain $domain");
if (!$this->connect()) {
return false;
}
if ($domain == NULL) {
$domain = $this->domain;
}
$result = $this->login($username, $password, $domain);
if (!$result) {
return false;
}
$_SESSION['user']->user_bind_dn = $result;
$_SESSION['user']->user_bind_pw = $password;
return $result;
}
public function domain_add($domain, $attributes = array())
{
if (empty($domain)) {
return false;
}
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$domain_base_dn = $this->conf->get('ldap', 'domain_base_dn');
$domain_name_attribute = $this->conf->get('ldap', 'domain_name_attribute');
$primary_domain = $this->conf->get('kolab', 'primary_domain');
$_primary_domain = str_replace('.', '_', $primary_domain);
$_domain = str_replace('.', '_', $domain);
if (empty($domain_name_attribute)) {
$domain_name_attribute = 'associateddomain';
}
if (!is_array($attributes[$domain_name_attribute])) {
$attributes[$domain_name_attribute] = (array) $attributes[$domain_name_attribute];
}
if (!in_array($domain, $attributes[$domain_name_attribute])) {
array_unshift($attributes[$domain_name_attribute], $domain);
}
$dn = $domain_name_attribute . '=' . $domain . ',' . $domain_base_dn;
$result = $this->add_entry($dn, $attributes);
if (!$result) {
return false;
}
if (!empty($attributes['inetdomainbasedn'])) {
$inetdomainbasedn = $attributes['inetdomainbasedn'];
}
else {
$inetdomainbasedn = $this->_standard_root_dn($domain);
}
$cn = str_replace(array(',', '='), array('\2C', '\3D'), $inetdomainbasedn);
$dn = "cn=" . $cn . ",cn=mapping tree,cn=config";
$attrs = array(
'objectclass' => array(
'top',
'extensibleObject',
'nsMappingTree',
),
'nsslapd-state' => 'backend',
'cn' => $inetdomainbasedn,
'nsslapd-backend' => $_domain,
);
$replica_hosts = $this->list_replicas();
if (!empty($replica_hosts)) {
foreach ($replica_hosts as $replica_host) {
Log::trace("Iterating over replication partners (now: $replica_host)");
$ldap = new Net_LDAP3($this->config);
$ldap->config_set("log_hook", array($this, "_log"));
$ldap->config_set('host', $replica_host);
$ldap->config_set('hosts', array($replica_host));
$ldap->connect();
$ldap->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$result = $ldap->add_entry($dn, $attrs);
if (!$result) {
Log::error("Error adding $dn to $replica_host");
}
$ldap->close();
}
} else {
$this->add_entry($dn, $attrs);
}
$result = $this->_read("cn=" . $_primary_domain . ",cn=ldbm database,cn=plugins,cn=config", array('nsslapd-directory'));
if (!$result) {
$result = $this->_read("cn=" . $primary_domain . ",cn=ldbm database,cn=plugins,cn=config", array('nsslapd-directory'));
}
if (!$result) {
$result = $this->_read("cn=userRoot,cn=ldbm database,cn=plugins,cn=config", array('nsslapd-directory'));
}
$this->_log(LOG_DEBUG, "Primary domain ldbm database configuration entry: " . var_export($result, true));
$result = $result[key($result)];
$orig_directory = $result['nsslapd-directory'];
$directory = str_replace($_primary_domain, $_domain, $result['nsslapd-directory']);
if ($directory == $orig_directory) {
$directory = str_replace($primary_domain, $_domain, $result['nsslapd-directory']);
}
if ($directory == $orig_directory) {
$directory = str_replace("userRoot", $_domain, $result['nsslapd-directory']);
}
$dn = "cn=" . $_domain . ",cn=ldbm database,cn=plugins,cn=config";
$attrs = array(
'objectclass' => array(
'top',
'extensibleobject',
'nsbackendinstance',
),
'cn' => $_domain,
'nsslapd-suffix' => $inetdomainbasedn,
'nsslapd-cachesize' => '-1',
'nsslapd-cachememsize' => '10485760',
'nsslapd-readonly' => 'off',
'nsslapd-require-index' => 'off',
'nsslapd-dncachememsize' => '10485760'
);
if (!empty($replica_hosts)) {
foreach ($replica_hosts as $replica_host) {
$ldap = new Net_LDAP3($this->config);
$ldap->config_set("log_hook", array($this, "_log"));
$ldap->config_set('host', $replica_host);
$ldap->config_set('hosts', array($replica_host));
$ldap->connect();
$ldap->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$ldap->config_set('return_attributes', array('nsslapd-directory'));
$result = $ldap->get_entry("cn=" . $_primary_domain . ",cn=ldbm database,cn=plugins,cn=config");
if (!$result) {
$result = $ldap->get_entry("cn=" . $primary_domain . ",cn=ldbm database,cn=plugins,cn=config");
}
if (!$result) {
$result = $ldap->get_entry("cn=userRoot,cn=ldbm database,cn=plugins,cn=config");
}
$this->_log(LOG_DEBUG, "Primary domain ldbm database configuration entry: " . var_export($result, true));
$result = $result[key($result)];
$orig_directory = $result['nsslapd-directory'];
$directory = str_replace($_primary_domain, $_domain, $result['nsslapd-directory']);
if ($directory == $orig_directory) {
$directory = str_replace($primary_domain, $_domain, $result['nsslapd-directory']);
}
if ($directory == $orig_directory) {
$directory = str_replace("userRoot", $_domain, $result['nsslapd-directory']);
}
$attrs['nsslapd-directory'] = $directory;
$ldap->add_entry($dn, $attrs);
$ldap->close();
}
} else {
$this->add_entry($dn, $attrs);
}
// Query the ACI for the primary domain
$domain_filter = $this->conf->get('ldap', 'domain_filter');
$domain_filter = '(&(' . $domain_name_attribute . '=' . $primary_domain . ')' . $domain_filter . ')';
$results = $this->_search($domain_base_dn, $domain_filter);
$entries = $results->entries(true);
$domain_entry = array_shift($entries);
if (in_array('inetdomainbasedn', $domain_entry)) {
$_base_dn = $domain_entry['inetdomainbasedn'];
}
else {
$_base_dn = $this->_standard_root_dn($primary_domain);
}
$result = $this->_read($_base_dn, array('aci'));
$result = $result[key($result)];
$acis = $result['aci'];
// Skip one particular ACI
foreach ($acis as $aci) {
if (stristr($aci, "SIE Group") === false) {
continue;
}
$_aci = $aci;
}
$service_bind_dn = $this->conf->get('ldap', 'service_bind_dn');
if (empty($service_bind_dn)) {
$service_bind_dn = $this->conf->get('ldap', 'bind_dn');
}
$dn = $inetdomainbasedn;
// @TODO: this list should be configurable or auto-created somehow
$self_attrs = array(
'carLicense', 'description', 'displayName', 'facsimileTelephoneNumber', 'homePhone',
'homePostalAddress', 'initials', 'jpegPhoto', 'labeledURI', 'mobile', 'o', 'pager', 'photo',
'postOfficeBox', 'postalAddress', 'postalCode', 'preferredDeliveryMethod', 'preferredLanguage',
'registeredAddress', 'roomNumber', 'secretary', 'seeAlso', 'st', 'street', 'telephoneNumber',
'telexNumber', 'title', 'userCertificate', 'userPassword', 'userSMIMECertificate',
'x500UniqueIdentifier',
);
if (in_array('kolabInetOrgPerson', $this->classes_allowed())) {
$self_attrs = array_merge($self_attrs, array('kolabDelegate', 'kolabInvitationPolicy', 'kolabAllowSMTPSender'));
}
$attrs = array(
// @TODO: Probably just use ldap_explode_dn()
'dc' => substr($dn, (strpos($dn, '=')+1), ((strpos($dn, ',')-strpos($dn, '='))-1)),
'objectclass' => array(
'top',
'domain',
),
'aci' => array(
// Self-modification
"(targetattr = \"" . implode(" || ", $self_attrs) . "\")(version 3.0; acl \"Enable self write for common attributes\"; allow (write) userdn=\"ldap:///self\";)",
// Directory Administrators
"(targetattr = \"*\")(version 3.0; acl \"Directory Administrators Group\"; allow (all) (groupdn=\"ldap:///cn=Directory Administrators," . $inetdomainbasedn . "\" or roledn=\"ldap:///cn=kolab-admin," . $inetdomainbasedn . "\");)",
// Configuration Administrators
"(targetattr = \"*\")(version 3.0; acl \"Configuration Administrators Group\"; allow (all) groupdn=\"ldap:///cn=Configuration Administrators,ou=Groups,ou=TopologyManagement,o=NetscapeRoot\";)",
// Administrator users
"(targetattr = \"*\")(version 3.0; acl \"Configuration Administrator\"; allow (all) userdn=\"ldap:///uid=admin,ou=Administrators,ou=TopologyManagement,o=NetscapeRoot\";)",
// SIE Group
$_aci,
// Search Access,
"(targetattr != \"userPassword\") (version 3.0; acl \"Search Access\"; allow (read,compare,search) (userdn = \"ldap:///" . $inetdomainbasedn . "??sub?(objectclass=*)\");)",
// Service Search Access
"(targetattr = \"*\") (version 3.0; acl \"Service Search Access\"; allow (read,compare,search) (userdn = \"ldap:///" . $service_bind_dn . "\");)",
),
);
if (!empty($replica_hosts)) {
foreach ($replica_hosts as $replica_host) {
$ldap = new Net_LDAP3($this->config);
$ldap->config_set("log_hook", array($this, "_log"));
$ldap->config_set('host', $replica_host);
$ldap->config_set('hosts', array($replica_host));
$ldap->connect();
$ldap->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$ldap->add_entry($dn, $attrs);
$ldap->close();
}
} else {
$this->add_entry($dn, $attrs);
}
if (!empty($replica_hosts)) {
$this->add_replication_agreements($inetdomainbasedn);
}
$dn = "cn=Directory Administrators," . $inetdomainbasedn;
$attrs = array(
'objectclass' => array(
'top',
'groupofuniquenames',
),
'cn' => 'Directory Administrators',
'uniquemember' => array(
'cn=Directory Manager'
),
);
$this->add_entry($dn, $attrs);
$dn = "ou=Groups," . $inetdomainbasedn;
$attrs = array(
'objectclass' => array('top', 'organizationalunit'),
'ou' => 'Groups',
);
$this->add_entry($dn, $attrs);
$dn = "ou=People," . $inetdomainbasedn;
$attrs = array(
'objectclass' => array('top', 'organizationalunit'),
'ou' => 'People',
);
$this->add_entry($dn, $attrs);
$dn = "ou=Special Users," . $inetdomainbasedn;
$attrs = array(
'objectclass' => array('top', 'organizationalunit'),
'ou' => 'Special Users',
);
$this->add_entry($dn, $attrs);
$dn = "ou=Resources," . $inetdomainbasedn;
$attrs = array(
'objectclass' => array('top', 'organizationalunit'),
'ou' => 'Resources',
);
$this->add_entry($dn, $attrs);
$dn = "ou=Shared Folders," . $inetdomainbasedn;
$attrs = array(
'objectclass' => array('top', 'organizationalunit'),
'ou' => 'Shared Folders',
);
$this->add_entry($dn, $attrs);
$dn = 'cn=kolab-admin,' . $inetdomainbasedn;
$attrs = array(
'objectclass' => array(
'top',
'ldapsubentry',
'nsroledefinition',
'nssimpleroledefinition',
'nsmanagedroledefinition',
),
'cn' => 'kolab-admin'
);
$this->add_entry($dn, $attrs);
return true;
}
public function domain_edit($domain, $attributes, $typeid = null)
{
$domain = $this->domain_info($domain, array_keys($attributes));
if (empty($domain)) {
return false;
}
$domain_dn = key($domain);
// We should start throwing stuff over the fence here.
return $this->modify_entry($domain_dn, $domain[$domain_dn], $attributes);
}
public function domain_delete($domain)
{
$base_dn = $this->conf->get('ldap', 'domain_base_dn');
return $this->entry_delete($domain, array(), $base_dn);
}
public function domain_find_by_attribute($attribute)
{
$base_dn = $this->conf->get('ldap', 'domain_base_dn');
return $this->entry_find_by_attribute($attribute, $base_dn);
}
public function domain_info($domain, $attributes = array('*'))
{
$this->_log(LOG_DEBUG, "Auth::LDAP::domain_info() for domain " . var_export($domain, true));
if (empty($domain)) {
return false;
}
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$domain_base_dn = $this->conf->get('ldap', 'domain_base_dn');
$domain_dn = $this->entry_dn($domain, array(), $domain_base_dn);
if (!$domain_dn) {
$domain_filter = $this->conf->get('ldap', 'domain_filter');
$domain_name_attribute = $this->conf->get('ldap', 'domain_name_attribute');
$domain_filter = "(&" . $domain_filter . "(" . $domain_name_attribute . "=" . $domain . "))";
$this->_log(LOG_DEBUG, "Auth::LDAP::domain_info() uses _search()");
if ($result = $this->_search($domain_base_dn, $domain_filter, $attributes)) {
$result = $result->entries(true);
}
}
else {
$this->_log(LOG_DEBUG, "Auth::LDAP::domain_info() uses _read()");
$result = $this->_read($domain_dn, $attributes);
}
if (!$result) {
return false;
}
$this->_log(LOG_DEBUG, "Auth::LDAP::domain_info() result: " . var_export($result, true));
return $result;
}
/**
* Proxy to parent function in order to enable us to insert our
* configuration.
*/
public function effective_rights($subject)
{
$cache = $this->get_cache();
$ckey = $_SESSION['user']->user_bind_dn . '#'
. md5($this->domain . '::' . $subject . '::' . $_SESSION['user']->user_bind_pw);
// use memcache
if ($cache && ($result = $cache->get($ckey))) {
return $result;
}
// use internal cache
else if (isset($this->icache[$ckey])) {
return $this->icache[$ckey];
}
// Ensure we are bound with the user's credentials
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$this->_log(LOG_DEBUG, "Auth::LDAP::effective_rights(\$subject = '" . $subject . "')");
switch ($subject) {
case "domain":
$result = parent::effective_rights($this->conf->get("ldap", "domain_base_dn"));
break;
case "group":
case "resource":
case "role":
case "sharedfolder":
case "user":
$result = parent::effective_rights($this->_subject_base_dn($subject));
break;
default:
$result = parent::effective_rights($subject);
}
if (!$result) {
$result = $this->legacy_rights($subject);
}
if ($cache) {
if (!$cache->replace($ckey, $result, MEMCACHE_COMPRESSED, 3600)) {
$cache->set($ckey, $result, MEMCACHE_COMPRESSED, 3600);
}
}
else {
$this->icache[$ckey] = $result;
}
return $result;
}
public function find_recipient($address)
{
+ if (strpos($address, '@') === false) {
+ return false;
+ }
+
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$mail_attrs = $this->conf->get_list('mail_attributes') ?: array('mail', 'alias');
$search = array('operator' => 'OR');
foreach ($mail_attrs as $num => $attr) {
$search['params'][$attr] = array(
'type' => 'exact',
'value' => $address,
);
}
$this->config_set('return_attributes', $mail_attrs);
$result = $this->search_entries($this->config_get('root_dn'), '(objectclass=*)', 'sub', null, $search);
if ($result && $result->count() > 0) {
- return $result->entries(TRUE);
+ $result = $result->entries(true);
+
+ // LDAP searches are case-insensitive, post-process result
+ // with correct character case handling
+ foreach ($result as $key => $user) {
+ foreach ($user as $attr => $list) {
+ foreach ((array) $list as $addr) {
+ if (compare_email($address, $addr)) {
+ continue 3;
+ }
+ }
+ }
+
+ unset($result[$key]);
+ }
+
+ reset($result);
+
+ return $result;
}
- return FALSE;
+ return false;
}
public function get_attributes($subject_dn, $attributes)
{
$this->_log(LOG_DEBUG, "Auth::LDAP::get_attributes() for $subject_dn");
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
return $this->get_entry_attributes($subject_dn, $attributes);
}
public function group_add($attrs, $typeid = null)
{
$base_dn = $this->entry_base_dn('group', $typeid);
// TODO: The rdn is configurable as well.
// Use [$type_str . "_"]user_rdn_attr
$dn = "cn=" . $attrs['cn'] . "," . $base_dn;
return $this->entry_add($dn, $attrs);
}
public function group_delete($group)
{
return $this->entry_delete($group);
}
public function group_edit($group, $attributes, $typeid = null)
{
$group = $this->group_info($group, array_keys($attributes));
if (empty($group)) {
return false;
}
$group_dn = key($group);
// We should start throwing stuff over the fence here.
return $this->modify_entry($group_dn, $group[$group_dn], $attributes);
}
public function group_find_by_attribute($attribute)
{
return $this->entry_find_by_attribute($attribute);
}
public function group_info($group, $attributes = array('*'))
{
$this->_log(LOG_DEBUG, "Auth::LDAP::group_info() for group " . var_export($group, true));
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$group_dn = $this->entry_dn($group);
if (!$group_dn) {
return false;
}
$this->read_prepare($attributes);
return $this->_read($group_dn, $attributes);
}
public function group_members_list($group, $recurse = true)
{
$group_dn = $this->entry_dn($group);
if (!$group_dn) {
return false;
}
return $this->list_group_members($group_dn, null, $recurse);
}
public function list_domains($attributes = array(), $search = array(), $params = array())
{
$this->_log(LOG_DEBUG, "Auth::LDAP::list_domains(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true));
$section = $this->conf->get('kolab', 'auth_mechanism');
$base_dn = $this->conf->get($section, 'domain_base_dn');
$filter = $this->conf->get($section, 'domain_filter');
$kolab_filter = $this->conf->get($section, 'kolab_domain_filter');
if (empty($filter) && !empty($kolab_filter)) {
$filter = $kolab_filter;
}
if (!$filter) {
$filter = "(associateddomain=*)";
}
return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params);
}
public function list_groups($attributes = array(), $search = array(), $params = array())
{
$this->_log(LOG_DEBUG, "Auth::LDAP::list_groups(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true));
$base_dn = $this->_subject_base_dn('group');
$filter = $this->conf->get('group_filter');
if (!$filter) {
$filter = "(|(objectclass=groupofuniquenames)(objectclass=groupofurls))";
}
return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params);
}
public function list_resources($attributes = array(), $search = array(), $params = array())
{
$this->_log(LOG_DEBUG, "Auth::LDAP::list_resources(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true));
$base_dn = $this->_subject_base_dn('resource');
$filter = $this->conf->get('resource_filter');
if (!$filter) {
$filter = "(&(objectclass=*)(!(objectclass=organizationalunit)))";
}
return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params);
}
public function list_roles($attributes = array(), $search = array(), $params = array())
{
$this->_log(LOG_DEBUG, "Auth::LDAP::list_roles(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true));
$base_dn = $this->_subject_base_dn('role');
$filter = $this->conf->get('role_filter');
if (empty($filter)) {
$filter = "(&(objectclass=ldapsubentry)(objectclass=nsroledefinition))";
}
return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params);
}
public function list_sharedfolders($attributes = array(), $search = array(), $params = array())
{
$this->_log(LOG_DEBUG, "Auth::LDAP::list_sharedfolders(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true));
$base_dn = $this->_subject_base_dn('sharedfolder');
$filter = $this->conf->get('sharedfolder_filter');
if (!$filter) {
$filter = "(&(objectclass=*)(!(objectclass=organizationalunit)))";
}
return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params);
}
public function list_users($attributes = array(), $search = array(), $params = array())
{
$this->_log(LOG_DEBUG, "Auth::LDAP::list_users(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true));
$base_dn = $this->_subject_base_dn('user');
$filter = $this->conf->get('user_filter');
if (empty($filter)) {
$filter = "(objectclass=kolabinetorgperson)";
}
return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params);
}
public function resource_add($attrs, $typeid = null)
{
$base_dn = $this->entry_base_dn('resource', $typeid);
// TODO: The rdn is configurable as well.
// Use [$type_str . "_"]user_rdn_attr
$dn = "cn=" . $attrs['cn'] . "," . $base_dn;
return $this->entry_add($dn, $attrs);
}
public function resource_delete($resource)
{
return $this->entry_delete($resource);
}
public function resource_edit($resource, $attributes, $typeid = null)
{
$resource = $this->resource_info($resource, array_keys($attributes));
if (empty($resource)) {
return false;
}
$resource_dn = key($resource);
// We should start throwing stuff over the fence here.
return $this->modify_entry($resource_dn, $resource[$resource_dn], $attributes);
}
public function resource_find_by_attribute($attribute)
{
return $this->entry_find_by_attribute($attribute);
}
public function resource_info($resource, $attributes = array('*'))
{
$this->_log(LOG_DEBUG, "Auth::LDAP::resource_info() for resource " . var_export($resource, true));
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$resource_dn = $this->entry_dn($resource);
if (!$resource_dn) {
return false;
}
$this->read_prepare($attributes);
return $this->_read($resource_dn, $attributes);
}
public function role_add($attrs)
{
$base_dn = $this->entry_base_dn('role', $typeid);
// TODO: The rdn is configurable as well.
// Use [$type_str . "_"]user_rdn_attr
$dn = "cn=" . $attrs['cn'] . "," . $base_dn;
return $this->entry_add($dn, $attrs);
}
public function role_edit($role, $attributes, $typeid = null)
{
$role = $this->role_info($role, array_keys($attributes));
if (empty($role)) {
return false;
}
$role_dn = key($role);
// We should start throwing stuff over the fence here.
return $this->modify_entry($role_dn, $role[$role_dn], $attributes);
}
public function role_delete($role)
{
return $this->entry_delete($role, array('objectclass' => 'ldapsubentry'));
}
public function role_find_by_attribute($attribute)
{
$attribute['objectclass'] = 'ldapsubentry';
return $this->entry_find_by_attribute($attribute);
}
public function role_info($role, $attributes = array('*'))
{
$this->_log(LOG_DEBUG, "Auth::LDAP::role_info() for role " . var_export($role, true));
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$role_dn = $this->entry_dn($role, array('objectclass' => 'ldapsubentry'));
if (!$role_dn) {
return false;
}
$this->read_prepare($attributes);
return $this->_read($role_dn, $attributes);
}
public function sharedfolder_add($attrs, $typeid = null)
{
$base_dn = $this->entry_base_dn('sharedfolder', $typeid);
// TODO: The rdn is configurable as well.
// Use [$type_str . "_"]user_rdn_attr
$dn = "cn=" . $attrs['cn'] . "," . $base_dn;
return $this->entry_add($dn, $attrs);
}
public function sharedfolder_delete($sharedfolder)
{
return $this->entry_delete($sharedfolder);
}
public function sharedfolder_edit($sharedfolder, $attributes, $typeid = null)
{
$sharedfolder = $this->sharedfolder_info($sharedfolder, array_keys($attributes));
if (empty($sharedfolder)) {
return false;
}
$sharedfolder_dn = key($sharedfolder);
// We should start throwing stuff over the fence here.
return $this->modify_entry($sharedfolder_dn, $sharedfolder[$sharedfolder_dn], $attributes);
}
public function sharedfolder_find_by_attribute($attribute)
{
return $this->entry_find_by_attribute($attribute);
}
public function sharedfolder_info($sharedfolder, $attributes = array('*'))
{
$this->_log(LOG_DEBUG, "Auth::LDAP::sharedfolder_info() for sharedfolder " . var_export($sharedfolder, true));
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$sharedfolder_dn = $this->entry_dn($sharedfolder);
if (!$sharedfolder_dn) {
return false;
}
$this->read_prepare($attributes);
return $this->_read($sharedfolder_dn, $attributes);
}
public function search($base_dn, $filter = '(objectclass=*)', $scope = 'sub', $sort = NULL, $search = array())
{
if (isset($_SESSION['user']->user_bind_dn) && !empty($_SESSION['user']->user_bind_dn)) {
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
}
$this->_log(LOG_DEBUG, "Relaying search to parent:" . var_export(func_get_args(), true));
return parent::search($base_dn, $filter, $scope, $sort, $search);
}
public function subject_base_dn($subject, $strict = false)
{
return $this->_subject_base_dn($subject, $strict);
}
public function user_add($attrs, $typeid = null)
{
$base_dn = $this->entry_base_dn('user', $typeid);
if (!empty($attrs['ou'])) {
$base_dn = $attrs['ou'];
}
// TODO: The rdn is configurable as well.
// Use [$type_str . "_"]user_rdn_attr
$dn = "uid=" . $attrs['uid'] . "," . $base_dn;
return $this->entry_add($dn, $attrs);
}
public function user_edit($user, $attributes, $typeid = null)
{
$user = $this->user_info($user, array_keys($attributes));
if (empty($user)) {
return false;
}
$user_dn = key($user);
// We should start throwing stuff over the fence here.
$result = $this->modify_entry($user_dn, $user[$user_dn], $attributes);
// Handle modification of current user data
if (!empty($result) && $user_dn == $_SESSION['user']->user_bind_dn) {
// update session password
if (!empty($result['replace']) && !empty($result['replace']['userpassword'])) {
$pass = $result['replace']['userpassword'];
$_SESSION['user']->user_bind_pw = is_array($pass) ? implode($pass) : $pass;
}
}
return $result;
}
public function user_delete($user)
{
return $this->entry_delete($user);
}
public function user_info($user, $attributes = array('*'))
{
$this->_log(LOG_DEBUG, "Auth::LDAP::user_info() for user " . var_export($user, true));
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$user_dn = $this->entry_dn($user);
if (!$user_dn) {
return false;
}
$this->read_prepare($attributes);
return $this->_read($user_dn, $attributes);
}
public function user_find_by_attribute($attribute)
{
return $this->entry_find_by_attribute($attribute);
}
/**
* Returns attributes available in specified object classes
*/
public function attributes_allowed($objectclasses = array())
{
$attributes = parent::attributes_allowed($objectclasses);
// additional special attributes that aren't in LDAP schema
$additional_attributes = array(
'top' => array('nsRoleDN'),
);
if (!empty($attributes)) {
foreach ($additional_attributes as $class => $attrs) {
if (in_array($class, $objectclasses)) {
$attributes['may'] = array_merge($attributes['may'], $attrs);
}
}
}
return $attributes;
}
/**
* Wrapper for search_entries()
*/
protected function _list($base_dn, $filter, $scope, $attributes, $search, $params)
{
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
if (!empty($params['sort_by'])) {
if (is_array($params['sort_by'])) {
foreach ($params['sort_by'] as $attrib) {
if (!in_array($attrib, $attributes)) {
$attributes[] = $attrib;
}
}
} else {
if (!in_array($params['sort_by'], $attributes)) {
$attributes[] = $params['sort_by'];
}
}
}
if (!empty($params['page_size'])) {
$this->config_set('page_size', $params['page_size']);
} else {
$this->config_set('page_size', 15);
}
if (!empty($params['page'])) {
$this->config_set('list_page', $params['page']);
} else {
$this->config_set('list_page', 1);
}
if (empty($attributes) || !is_array($attributes)) {
$attributes = array('*');
}
$this->config_set('return_attributes', $attributes);
$result = $this->search_entries($base_dn, $filter, $scope, null, $search);
$entries = $this->sort_and_slice($result, $params);
return array(
'list' => $entries,
'count' => is_object($result) ? $result->count() : 0,
);
}
/**
* Prepare environment before _read() call
*/
protected function read_prepare(&$attributes)
{
// always return unique attribute
$unique_attr = $this->conf->get('unique_attribute');
if (empty($unique_attr)) {
$unique_attr = 'nsuniqueid';
}
Log::trace("Using unique_attribute " . var_export($unique_attr, TRUE) . " at " . __FILE__ . ":" . __LINE__);
if (!in_array($unique_attr, $attributes)) {
$attributes[] = $unique_attr;
}
}
/**
* delete_entry() wrapper with binding and DN resolving
*/
protected function entry_delete($entry, $attributes = array(), $base_dn = null)
{
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
$entry_dn = $this->entry_dn($entry, $attributes, $base_dn);
// object not found or self deletion
if (!$entry_dn || $entry_dn == $_SESSION['user']->user_bind_dn) {
return false;
}
return $this->delete_entry($entry_dn);
}
/**
* add_entry() wrapper with binding
*/
protected function entry_add($entry_dn, $attrs)
{
$this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
return $this->add_entry($entry_dn, $attrs);
}
/**
* Return base DN for specified object type
*/
protected function entry_base_dn($type, $typeid = null)
{
if ($typeid) {
$db = SQL::get_instance();
$sql = $db->fetch_assoc($db->query("SELECT `key` FROM {$type}_types WHERE id = ?", $typeid));
// Check if the type has a specific base DN specified.
$base_dn = $this->_subject_base_dn($sql['key'] . '_' . $type, true);
}
if (empty($base_dn)) {
$base_dn = $this->_subject_base_dn($type);
}
return $base_dn;
}
public function _config_get($key, $default = NULL)
{
$key_parts = explode("_", $key);
$this->_log(LOG_DEBUG, var_export($key_parts));
while (!empty($key_parts)) {
$value = $this->conf->get(implode("_", $key_parts));
if (empty($value)) {
$_discard = array_shift($key_parts);
} else {
break;
}
}
if (empty($value)) {
return $default;
} else {
return $value;
}
}
public function _log($level, $msg)
{
if (strstr($_SERVER["REQUEST_URI"], "/api/")) {
$str = "(api) ";
} else {
$str = "";
}
if (is_array($msg)) {
$msg = implode("\n", $msg);
}
switch ($level) {
case LOG_DEBUG:
Log::debug($str . $msg);
break;
case LOG_ERR:
Log::error($str . $msg);
break;
case LOG_INFO:
Log::info($str . $msg);
break;
case LOG_WARNING:
Log::warning($str . $msg);
break;
case LOG_ALERT:
case LOG_CRIT:
case LOG_EMERG:
case LOG_NOTICE:
default:
Log::trace($str . $msg);
break;
}
}
private function _subject_base_dn($subject, $strict = false)
{
$subject_base_dn = $this->conf->get_raw($this->domain, $subject . "_base_dn");
if (empty($subject_base_dn)) {
$subject_base_dn = $this->conf->get_raw("ldap", $subject . "_base_dn");
}
if (empty($subject_base_dn) && $strict) {
$this->_log(LOG_DEBUG, "subject_base_dn for subject $subject not found");
return null;
}
// Attempt to get a configured base_dn
$base_dn = $this->conf->get($this->domain, "base_dn");
if (empty($base_dn)) {
$base_dn = $this->domain_root_dn($this->domain);
}
if (!empty($subject_base_dn)) {
$base_dn = $this->conf->expand($subject_base_dn, array("base_dn" => $base_dn));
}
$this->_log(LOG_DEBUG, "subject_base_dn for subject $subject is $base_dn");
return $base_dn;
}
private function legacy_rights($subject)
{
$subject_dn = $this->entry_dn($subject);
$user_is_admin = false;
$user_is_self = false;
// List group memberships
$user_groups = $this->find_user_groups($_SESSION['user']->user_bind_dn);
console("User's groups", $user_groups);
foreach ($user_groups as $user_group_dn) {
if ($user_is_admin)
continue;
$user_group_dn_components = ldap_explode_dn($user_group_dn, 1);
unset($user_group_dn_components["count"]);
$user_group_cn = array_shift($user_group_dn_components);
if (in_array($user_group_cn, array('admin', 'maintainer', 'domain-maintainer'))) {
// All rights default to write.
$user_is_admin = true;
} else {
// The user is a regular user, see if the subject is the same has the
// user session's bind_dn.
if ($subject_dn == $_SESSION['user']->user_bind_dn) {
$user_is_self = true;
}
}
}
if ($user_is_admin) {
$standard_rights = array("add", "delete", "read", "write");
} elseif ($user_is_self) {
$standard_rights = array("read", "write");
} else {
$standard_rights = array("read");
}
$rights = array(
'entryLevelRights' => $standard_rights,
'attributeLevelRights' => array(),
);
$subject = $this->_search($subject_dn);
if (!$subject) {
return $rights;
}
$subject = $subject->entries(true);
$attributes = $this->attributes_allowed($subject[$subject_dn]['objectclass']);
$attributes = array_merge($attributes['may'], $attributes['must']);
foreach ($attributes as $attribute) {
$rights['attributeLevelRights'][$attribute] = $standard_rights;
}
return $rights;
}
private function sort_and_slice(&$result, &$params)
{
if (!is_object($result)) {
return array();
}
$entries = $result->entries(true);
if ($this->vlv_active) {
return $entries;
}
if (!empty($params) && is_array($params)) {
if (!empty($params['sort_by'])) {
$this->sort_result_key = $params['sort_by'];
uasort($entries, array($this, 'sort_result'));
}
if (!empty($params['sort_order']) && $params['sort_order'] == "DESC") {
$entries = array_reverse($entries, true);
}
if (!empty($params['page_size']) && !empty($params['page'])) {
if ($result->count() > $params['page_size']) {
$entries = array_slice($entries, (($params['page'] - 1) * $params['page_size']), $params['page_size'], true);
}
}
}
return $entries;
}
/**
* Result sorting callback for uasort()
*/
private function sort_result($a, $b)
{
if (is_array($this->sort_result_key)) {
foreach ($this->sort_result_key as $attrib) {
if (array_key_exists($attrib, $a) && !$str1) {
$str1 = $a[$attrib];
}
if (array_key_exists($attrib, $b) && !$str2) {
$str2 = $b[$attrib];
}
}
} else {
$str1 = $a[$this->sort_result_key];
$str2 = $b[$this->sort_result_key];
}
if (is_array($str1)) {
$str1 = array_shift($str1);
}
if (is_array($str2)) {
$str2 = array_shift($str2);
}
return strcmp(mb_strtoupper($str1), mb_strtoupper($str2));
}
/**
* Qualify a username.
*
* Where username is 'kanarip@kanarip.com', the function will return an
* array containing 'kanarip' and 'kanarip.com'. However, where the
* username is 'kanarip', the domain name is to be assumed the
* management domain name.
*/
private function _qualify_id($username)
{
$username_parts = explode('@', $username);
if (count($username_parts) == 1) {
$domain_name = $this->conf->get('primary_domain');
}
else {
$domain_name = array_pop($username_parts);
}
return array(implode('@', $username_parts), $domain_name);
}
/***********************************************************
************ Shortcut functions ****************
***********************************************************/
/**
* Translate a domain name into it's corresponding root dn.
*/
private function domain_root_dn($domain)
{
if (!$this->connect()) {
$this->_log(LOG_DEBUG, "Could not connect");
return false;
}
$bind_dn = $this->config_get("service_bind_dn", $this->conf->get("service_bind_dn"));
$bind_pw = $this->config_get("service_bind_pw", $this->conf->get("service_bind_pw"));
if (!$this->bind($bind_dn, $bind_pw)) {
$this->_log(LOG_DEBUG, "Could not connect");
return false;
}
$this->_log(LOG_DEBUG, "Auth::LDAP::domain_root_dn(\$domain = $domain) called");
if (empty($domain)) {
return false;
}
$domain_base_dn = $this->conf->get('ldap', 'domain_base_dn');
$domain_filter = $this->conf->get('ldap', 'domain_filter');
$domain_name_attribute = $this->conf->get('ldap', 'domain_name_attribute');
if (empty($domain_name_attribute)) {
$domain_name_attribute = 'associateddomain';
}
$domain_filter = "(&" . $domain_filter . "(" . $domain_name_attribute . "=" . $domain . "))";
$result = $this->_search($domain_base_dn, $domain_filter);
if (!$result) {
return $this->_standard_root_dn($domain);
}
$entries = $result->entries(true);
$entry_dn = key($entries);
$entry_attrs = $entries[$entry_dn];
if (is_array($entry_attrs)) {
if (array_key_exists('inetdomainbasedn', $entry_attrs) && !empty($entry_attrs['inetdomainbasedn'])) {
$domain_root_dn = $entry_attrs['inetdomainbasedn'];
}
else {
if (is_array($entry_attrs[$domain_name_attribute])) {
$domain_root_dn = $this->_standard_root_dn($entry_attrs[$domain_name_attribute][0]);
}
else {
$domain_root_dn = $this->_standard_root_dn($entry_attrs[$domain_name_attribute]);
}
}
}
else {
$domain_root_dn = $this->_standard_root_dn($domain);
}
return $domain_root_dn;
}
/**
* Probe the root dn with the user credentials.
*
* When a list of domains is retrieved, this does not mean the user
* actually has access. Given the root dn for each domain however, we
* can in fact attempt to list / search the root dn and see if we get
* any results. If we don't, maybe this user is not authorized for the
* domain at all?
*/
private function _probe_root_dn($entry_root_dn)
{
//console("Running for entry root dn: " . $entry_root_dn);
if (($tmpconn = ldapconnect($this->_ldap_server)) == false) {
//message("LDAP Error: " . $this->_errstr());
return false;
}
//console("User DN: " . $_SESSION['user']->user_bind_dn);
if (ldap_bind($tmpconn, $_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw) === false) {
//message("LDAP Error: " . $this->_errstr());
return false;
}
if (($list_success = ldap_list($tmpconn, $entry_root_dn, '(objectClass=*)', array('*', 'aci'))) === false) {
//message("LDAP Error: " . $this->_errstr());
return false;
}
return true;
}
private function _read($entry_dn, $attributes = array('*'))
{
$this->config_set('return_attributes', $attributes);
$result = $this->search($entry_dn, '(objectclass=*)', 'base');
if ($result) {
$this->_log(LOG_DEBUG, "Auth::LDAP::_read() result: " . var_export($result->entries(true), true));
return $result->entries(true);
} else {
return false;
}
}
private function _search($base_dn, $filter = '(objectclass=*)', $attributes = array('*'))
{
$this->config_set('return_attributes', $attributes);
$result = $this->search($base_dn, $filter);
$this->_log(LOG_DEBUG, "Auth::LDAP::_search on $base_dn with $filter for attributes: " . var_export($attributes, true) . " with result: " . var_export($result, true));
return $result;
}
/**
* From a domain name, such as 'kanarip.com', create a standard root
* dn, such as 'dc=kanarip,dc=com'.
*
* As the parameter $associatedDomains, either pass it an array (such
* as may have been returned by ldap_get_entries() or perhaps
* ldap_list()), where the function will assume the first value
* ($array[0]) to be the uber-level domain name, or pass it a string
* such as 'kanarip.nl'.
*
* @return string
*/
private function _standard_root_dn($associatedDomains)
{
if (is_array($associatedDomains)) {
// Usually, the associatedDomain in position 0 is the naming attribute associatedDomain
if ($associatedDomains['count'] > 1) {
// Issue a debug message here
$relevant_associatedDomain = $associatedDomains[0];
}
else {
$relevant_associatedDomain = $associatedDomains[0];
}
}
else {
$relevant_associatedDomain = $associatedDomains;
}
return "dc=" . implode(',dc=', explode('.', $relevant_associatedDomain));
}
/**
* Get global handle for memcache access
*
* @return object Memcache
*/
public function get_cache()
{
if (!isset($this->memcache)) {
// no memcache support in PHP
if (!class_exists('Memcache')) {
$this->memcache = false;
return false;
}
// add all configured hosts to pool
$pconnect = $this->conf->get('kolab_wap', 'memcache_pconnect', Conf::BOOL);
$hosts = $this->conf->get('kolab_wap', 'memcache_hosts');
if ($hosts) {
$this->memcache = new Memcache;
$this->mc_available = 0;
$hosts = explode(',', $hosts);
foreach ($hosts as $host) {
$host = trim($host);
if (substr($host, 0, 7) != 'unix://') {
list($host, $port) = explode(':', $host);
if (!$port) $port = 11211;
}
else {
$port = 0;
}
$this->mc_available += intval($this->memcache->addServer(
$host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure')));
}
}
// test connection and failover (will result in $this->mc_available == 0 on complete failure)
$this->memcache->increment('__CONNECTIONTEST__', 1); // NOP if key doesn't exist
if (!$this->mc_available) {
$this->memcache = false;
}
}
return $this->memcache;
}
/**
* Callback for memcache failure
*/
public function memcache_failure($host, $port)
{
static $seen = array();
// only report once
if (!$seen["$host:$port"]++) {
$this->mc_available--;
Log::error("Memcache failure on host $host:$port");
}
}
}
diff --git a/lib/api/kolab_api_service_form_value.php b/lib/api/kolab_api_service_form_value.php
index 0ac7420..ffc3750 100644
--- a/lib/api/kolab_api_service_form_value.php
+++ b/lib/api/kolab_api_service_form_value.php
@@ -1,1480 +1,1482 @@
<?php
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab Web Admin Panel |
| |
| Copyright (C) 2011-2012, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
| Author: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
/**
* Service providing functionality related to HTML forms generation/validation.
*/
class kolab_api_service_form_value extends kolab_api_service
{
const VALIDATE_DEFAULT = 'default';
const VALIDATE_BASIC = 'basic';
const VALIDATE_EXTENDED = 'extended';
const VALIDATE_NONE = 'none';
/**
* Returns service capabilities.
*
* @param string $domain Domain name
*
* @return array Capabilities list
*/
public function capabilities($domain)
{
return array(
'generate' => 'r',
'validate' => 'r',
'select_options' => 'r',
'list_options' => 'r',
);
}
/**
* Generation of auto-filled field values.
*
* @param array $getdata GET parameters
* @param array $postdata POST parameters. Required parameters:
* - attributes: list of attribute names
* - type_id: Type identifier
* - object_type: Object type (user, group, etc.)
*
* @return array Response with attribute name as a key
*/
public function generate($getdata, $postdata)
{
$attribs = $this->object_type_attributes($postdata['object_type'], $postdata['type_id'], $type_key);
$attributes = (array) $postdata['attributes'];
$result = array();
$postdata['type_key'] = $type_key;
foreach ($attributes as $attr_name) {
if (empty($attr_name)) {
continue;
}
$method_name = 'generate_' . str_replace('-', '_', strtolower($attr_name)) . '_' . strtolower($postdata['object_type']);
if (!method_exists($this, $method_name)) {
Log::trace("Method $method_name doesn't exist");
$method_name = 'generate_' . str_replace('-', '_', strtolower($attr_name));
if (!method_exists($this, $method_name)) {
Log::trace("Method $method_name doesn't exist either");
continue;
}
}
Log::trace("Executing method $method_name");
$result[$attr_name] = $this->{$method_name}($postdata, $attribs);
}
Log::trace("Returning result: " . var_export($result, TRUE));
return $result;
}
/**
* Generation of values for fields of type LIST.
*
* @param array $getdata GET parameters
* @param array $postdata POST parameters. Required parameters:
* - attribute: attribute name
* - type_id: Type identifier
* - object_type: Object type (user, group, etc.)
*
* @return array Response with attribute name as a key
*/
public function list_options($getdata, $postdata)
{
//console($postdata);
$attribs = $this->object_type_attributes($postdata['object_type'], $postdata['type_id'], $key_name);
$attr_name = $postdata['attribute'];
$result = array(
// return search value, so client can match response to request
'search' => $postdata['search'],
'list' => array(),
);
if (empty($attr_name)) {
return $result;
}
if ($key_name) {
$postdata['type_key'] = $key_name;
}
$method_name = 'list_options_' . strtolower($attr_name) . '_' . strtolower($postdata['object_type']);
if (!method_exists($this, $method_name)) {
//console("Method $method_name doesn't exist");
$method_name = 'list_options_' . strtolower($attr_name);
if (!method_exists($this, $method_name)) {
return $result;
}
}
//console($method_name);
$result['list'] = $this->{$method_name}($postdata, $attribs);
return $result;
}
/**
* Generation of values for fields of type SELECT.
*
* @param array $getdata GET parameters
* @param array $postdata POST parameters. Required parameters:
* - attributes: list of attribute names
* - type_id: Type identifier
* - object_type: Object type (user, group, etc.)
*
* @return array Response with attribute name as a key
*/
public function select_options($getdata, $postdata)
{
//console("form_value.select_options postdata", $postdata);
$attribs = $this->object_type_attributes($postdata['object_type'], $postdata['type_id'], $key_name);
$attributes = (array) $postdata['attributes'];
$result = array();
if ($key_name) {
$postdata['type_key'] = $key_name;
}
foreach ($attributes as $attr_name) {
if (empty($attr_name)) {
continue;
}
$method_name = 'select_options_' . strtolower($attr_name);
if (method_exists($this, $method_name)) {
$res = $this->{$method_name}($postdata, $attribs);
}
else {
$res = array();
}
if (!is_array($res['list'])) {
$res['list'] = array();
}
$result[$attr_name] = $res;
}
return $result;
}
/**
* Validation of field values.
*
* @param array $getdata GET parameters
* @param array $postdata POST parameters. Required parameters:
* - type_id: Type identifier
* - object_type: Object type (user, group, etc.)
*
* @return array Response with attribute name as a key
*/
public function validate($getdata, $postdata)
{
$attribs = $this->object_type_attributes($postdata['object_type'], $postdata['type_id']);
$result = array();
Log::trace("kolab_api_form_value::validate() \$postdata: " . var_export($postdata, TRUE));
foreach ((array)$postdata as $attr_name => $attr_value) {
if (empty($attr_name) || $attr_name == 'type_id' || $attr_name == 'object_type') {
continue;
}
$method_name = 'validate_' . strtolower($attr_name) . '_' . strtolower($postdata['object_type']);
if (!method_exists($this, $method_name)) {
//console("Method $method_name doesn't exist");
$method_name = 'validate_' . strtolower($attr_name);
if (!method_exists($this, $method_name)) {
$result[$attr_name] = 'OK';
continue;
}
}
$validate = $attribs['form_fields'][$attr_name]['validate'];
// deprecated value: false = VALIDATE_NONE
if ($validate === false) {
$validate = self::VALIDATE_NONE;
}
if ($validate == self::VALIDATE_NONE) {
$result[$attr_name] = $attr_value;
}
else if ($attribs['form_fields'][$attr_name]['optional'] && $attr_value === '') {
$result[$attr_name] = $attr_value;
}
else {
$result[$attr_name] = $this->{$method_name}($attr_value, $postdata, $validate);
}
}
Log::trace("kolab_api_form_value::validate() \$result: " . var_export($result, TRUE));
return $result;
}
private function generate_alias($postdata, $attribs = array())
{
$rcpt_pol_aliases = $this->generate_secondary_mail($postdata, $attribs);
$service = $this->controller->get_service('user');
$user_attrs = $service->user_info(array('id' => $postdata['id']), null);
if (!empty($user_attrs['alias'])) {
$cur_aliases = $user_attrs['alias'];
} else {
$cur_aliases = Array();
}
if (!is_array($cur_aliases)) {
$cur_aliases = (array)($cur_aliases);
}
sort($rcpt_pol_aliases);
sort($cur_aliases);
$form_aliases = array_unique(array_merge($rcpt_pol_aliases, $cur_aliases));
Log::trace("kolab_api_service_form_value::generate_alias() \$form_aliases: " . var_export($form_aliases, TRUE));
return array_values($form_aliases);
}
private function generate_apple_generateduid($postdata, $attribs = array())
{
if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['apple-generateduid'])) {
$uuid = exec("uuidgen | tr '[:lower:]' '[:upper:]'");
return $uuid;
}
}
private function generate_astaccountcallerid($postdata, $attribs = array())
{
if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['astaccountcallerid'])) {
// Use Data Please
foreach ($attribs['auto_form_fields']['astaccountcallerid']['data'] as $key) {
if (!isset($postdata[$key])) {
throw new Exception("Key not set: " . $key, 12356);
}
}
// TODO: Correct this with what is in 'data'...
return $this->generate_cn($postdata, $attribs);
}
}
private function generate_astaccountdefaultuser($postdata, $attribs = array())
{
if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['astaccountdefaultuser'])) {
// Use Data Please
foreach ($attribs['auto_form_fields']['astaccountdefaultuser']['data'] as $key) {
if (!isset($postdata[$key])) {
throw new Exception("Key not set: " . $key, 12356);
}
}
return $this->generate_uid($postdata, $attribs);
}
}
private function generate_astaccountmailbox($postdata, $attribs = array())
{
if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['astaccountmailbox'])) {
// Use Data Please
foreach ($attribs['auto_form_fields']['astaccountmailbox']['data'] as $key) {
if (!isset($postdata[$key])) {
throw new Exception("Key not set: " . $key, 12356);
}
}
return $this->generate_uid($postdata, $attribs);
}
}
private function generate_astaccountregistrationcontext($postdata, $attribs = array())
{
if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['astaccountregistrationcontext'])) {
// Use Data Please
foreach ($attribs['auto_form_fields']['astaccountregistrationcontext']['data'] as $key) {
if (!isset($postdata[$key])) {
throw new Exception("Key not set: " . $key, 12356);
}
}
return $this->generate_uid($postdata, $attribs);
}
}
private function generate_astaccountregistrationexten($postdata, $attribs = array())
{
if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['astaccountregistrationexten'])) {
$search = array(
'params' => array(
'objectclass' => array(
'type' => 'exact',
'value' => 'asterisksipuser',
),
),
);
$auth = Auth::get_instance($_SESSION['user']->get_domain());
$conf = Conf::get_instance();
$users = $auth->list_users(NULL, Array('astaccountregistrationexten'), $search);
$lower_astaccountregistrationexten = $conf->get('astaccountregistrationexten_lower_barrier');
if (!$lower_astaccountregistrationexten) {
$lower_astaccountregistrationexten = 200;
}
// Start at the lower barrier + 1
$lower_astaccountregistrationexten = ($lower_astaccountregistrationexten + 1);
$higher_astaccountregistrationexten = $conf->get('astaccountregistrationexten_higher_barrier');
if (!$higher_astaccountregistrationexten) {
$higher_astaccountregistrationexten = 300;
}
$astaccountregistrationextens = Array();
foreach ($users['list'] as $dn => $attributes) {
if (!array_key_exists('astaccountregistrationexten', $attributes)) {
continue;
}
if ($attributes['astaccountregistrationexten'] > $highest_astaccountregistrationexten) {
$astaccountregistrationextens[] = $attributes['astaccountregistrationexten'];
}
}
for ($i = $lower_astaccountregistrationexten; $i < $higher_astaccountregistrationexten; $i++) {
if (!in_array($i, $astaccountregistrationextens)) {
$astaccountregistrationexten = $i;
break;
}
}
return $astaccountregistrationexten;
}
}
private function generate_cn($postdata, $attribs = array())
{
if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['cn'])) {
// Use Data Please
foreach ($attribs['auto_form_fields']['cn']['data'] as $key) {
if (!isset($postdata[$key])) {
throw new Exception("Key not set: " . $key, 12356);
}
}
// TODO: Generate using policy from configuration
$cn = trim($postdata['givenname'] . " " . $postdata['sn']);
return $cn;
}
}
private function generate_cn_resource($postdata, $attribs = array())
{
if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['cn'])) {
// Use Data Please
foreach ($attribs['auto_form_fields']['cn']['data'] as $key) {
if (!isset($postdata[$key])) {
throw new Exception("Key not set: " . $key, 12356);
}
}
$auth = Auth::get_instance($_SESSION['user']->get_domain());
$cn = $postdata['cn'];
$x = 2;
while (($resource_found = $auth->resource_find_by_attribute(array('cn' => $cn)))) {
if (!empty($postdata['id'])) {
$resource_found_dn = key($resource_found);
$resource_found_unique_attr = $this->unique_attribute_value($resource_found_dn);
//console("resource with mail $mail found", $resource_found_unique_attr);
if ($resource_found_unique_attr == $postdata['id']) {
//console("that's us.");
break;
}
}
$cn = $postdata['cn'] . ' #' . $x;
$x++;
}
return $cn;
}
}
private function generate_displayname($postdata, $attribs = array())
{
if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['displayname'])) {
// Use Data Please
foreach ($attribs['auto_form_fields']['displayname']['data'] as $key) {
if (!isset($postdata[$key])) {
throw new Exception("Key not set: " . $key, 12356);
}
}
// TODO: Generate using policy from configuration
$displayname = $postdata['givenname'];
if ($postdata['sn']) {
$displayname = $postdata['sn'] . ", " . $displayname;
}
// TODO: Figure out what may be sent as an additional comment;
//
// Examples:
//
// - van Meeuwen, Jeroen (Kolab Systems)
// - Doe, John (Contractor)
//
return $displayname;
}
}
private function generate_gidnumber($postdata, $attribs = array())
{
if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['gidnumber'])) {
$auth = Auth::get_instance($_SESSION['user']->get_domain());
$conf = Conf::get_instance();
// TODO: Take a policy to use a known group ID, a known group (by name?)
// and/or create user private groups.
// groups search parameters
$params = array('page_size' => 200);
$search = array(
'operator' => 'AND',
'params' => array(
'objectclass' => array(
'type' => 'exact',
'value' => 'posixgroup',
),
'gidnumber' => array(
'type' => '>=',
)
),
);
$highest_gidnumber = $conf->get('gidnumber_lower_barrier');
if (!$highest_gidnumber) {
$highest_gidnumber = 999;
}
do {
$search['params']['gidnumber']['value'] = $highest_gidnumber;
$groups = $auth->list_groups(NULL, array('gidnumber'), $search, $params);
foreach ($groups['list'] as $dn => $attributes) {
if (!array_key_exists('gidnumber', $attributes)) {
continue;
}
if ($attributes['gidnumber'] > $highest_gidnumber) {
$highest_gidnumber = $attributes['gidnumber'];
}
}
}
while ($groups['count'] == $params['page_size']);
$gidnumber = ($highest_gidnumber + 1);
$postdata['gidnumber'] = $gidnumber;
if (empty($postdata['uidnumber'])) {
$uidnumber = $this->generate_uidnumber($postdata, $attribs);
$gidnumber = max($uidnumber, $gidnumber);
}
return $gidnumber;
}
}
private function generate_homedirectory($postdata, $attribs = array())
{
if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['homedirectory'])) {
// Use Data Please
foreach ($attribs['auto_form_fields']['homedirectory']['data'] as $key) {
if (!isset($postdata[$key])) {
throw new Exception("Key not set: " . $key, 12356);
}
}
// TODO: Home directory attribute to use
$uid = $this->generate_uid($postdata, $attribs);
// TODO: Home directory base path from configuration?
$conf = Conf::get_instance();
$homedirectory_base = $conf->get($_SESSION['user']->get_domain(), 'homedirectory_base');
if (empty($homedirectory_base)) {
$homedirectory_base = $conf->get('kolab', 'homedirectory_base');
}
if (empty($homedirectory_base)) {
$homedirectory_base = "/home/";
}
if (substr($homedirectory_base, (strlen($homedirectory_base)-1), 1) == "/") {
$homedirectory_base = substr($homedirectory_base, 0, (strlen($homedirectory_base)-1));
}
return $homedirectory_base . '/' . $uid;
}
}
private function generate_kolabtargetfolder_resource($postdata, $attribs = array())
{
if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['kolabtargetfolder'])) {
// Use Data Please
foreach ($attribs['auto_form_fields']['kolabtargetfolder']['data'] as $key) {
if (!isset($postdata[$key])) {
throw new Exception("Key not set: " . $key, 12356);
}
}
// TODO: Detect or from config
$imap_hierarchysep = '/';
$cn = $this->generate_cn_resource($postdata, $attribs);
return 'shared' . $imap_hierarchysep . 'Resources' . $imap_hierarchysep . $cn . '@' . $_SESSION['user']->get_domain();
}
}
private function generate_mail($postdata, $attribs = array())
{
return $this->generate_primary_mail($postdata, $attribs);
}
private function generate_mail_group($postdata, $attribs = array())
{
return $this->generate_primary_mail_group($postdata, $attribs);
}
private function generate_mail_resource($postdata, $attribs = array())
{
if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['mail'])) {
// Use Data Please
foreach ($attribs['auto_form_fields']['mail']['data'] as $key) {
if (!isset($postdata[$key])) {
throw new Exception("Key not set: " . $key, 12356);
}
}
$resourcedata = kolab_recipient_policy::normalize_groupdata($postdata);
//console("normalized resource data", $resourcedata);
// TODO: Normalize $postdata
$mail_local = 'resource-' . $postdata['type_key'] . '-' . strtolower($resourcedata['cn']);
$mail_domain = $_SESSION['user']->get_domain();
$mail = $mail_local . '@' . $mail_domain;
$auth = Auth::get_instance($_SESSION['user']->get_domain());
$x = 2;
while (($resource_found = $auth->resource_find_by_attribute(array('mail' => $mail)))) {
if (!empty($postdata['id'])) {
$resource_found_dn = key($resource_found);
$resource_found_unique_attr = $this->unique_attribute_value($resource_found_dn);
//console("resource with mail $mail found", $resource_found_unique_attr);
if ($resource_found_unique_attr == $postdata['id']) {
//console("that's us.");
break;
}
}
$mail = $mail_local . '-' . $x . '@' . $mail_domain;
$x++;
}
return $mail;
}
}
private function generate_mailalternateaddress($postdata, $attribs = array())
{
return $this->generate_secondary_mail($postdata, $attribs);
}
private function generate_mailhost($postdata, $attribs = array())
{
if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['uidnumber'])) {
// This value is determined by the Kolab Daemon
return '';
}
}
private function generate_password($postdata, $attribs = array())
{
// TODO: Password complexity policy.
exec("head -c 200 /dev/urandom | tr -dc _A-Z-a-z-0-9 | head -c15", $userpassword_plain);
return $userpassword_plain[0];
}
private function generate_userpassword($postdata, $attribs = array())
{
return $this->generate_password($postdata, $attribs);
}
private function generate_primary_mail($postdata, $attribs = array())
{
if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['mail'])) {
// Use Data Please
foreach ($attribs['auto_form_fields']['mail']['data'] as $key) {
if (!isset($postdata[$key])) {
throw new Exception("Key not set: " . $key, 12356);
}
}
if (array_key_exists('uid', $attribs['auto_form_fields'])) {
if (!array_key_exists('uid', $postdata)) {
$postdata['uid'] = $this->generate_uid($postdata, $attribs);
}
}
$primary_mail = kolab_recipient_policy::primary_mail($postdata);
return $primary_mail;
}
}
private function generate_primary_mail_group($postdata, $attribs = array())
{
if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['mail'])) {
// Use Data Please
foreach ($attribs['auto_form_fields']['mail']['data'] as $key) {
if (!isset($postdata[$key])) {
throw new Exception("Key not set: " . $key, 12356);
}
}
$primary_mail = kolab_recipient_policy::primary_mail_group($postdata);
return $primary_mail;
}
}
private function generate_secondary_mail($postdata, $attribs = array())
{
if (isset($attribs['auto_form_fields'])) {
if (isset($attribs['auto_form_fields']['alias'])) {
$secondary_mail_key = 'alias';
} elseif (isset($attribs['auto_form_fields']['mailalternateaddress'])) {
$secondary_mail_key = 'mailalternateaddress';
} else {
throw new Exception("No valid input for secondary mail address(es)", 478);
}
foreach ($attribs['auto_form_fields'][$secondary_mail_key]['data'] as $key) {
if (!isset($postdata[$key])) {
throw new Exception("Key not set: " . $key, 456789);
}
}
if (array_key_exists('uid', $attribs['auto_form_fields'])) {
if (!array_key_exists('uid', $postdata)) {
$postdata['uid'] = $this->generate_uid($postdata, $attribs);
}
}
if (array_key_exists('mail', $attribs['auto_form_fields'])) {
if (!array_key_exists('mail', $postdata)) {
$postdata['mail'] = $this->generate_primary_mail($postdata, $attribs);
}
}
$auth = Auth::get_instance();
$_secondary_mail_addresses = kolab_recipient_policy::secondary_mail($postdata);
$secondary_mail_addresses = array();
foreach ($_secondary_mail_addresses as $num => $alias) {
list($_local, $_domain) = explode("@", $alias);
$local = $_local;
$x = 2;
while (($user_found = $auth->find_recipient($local . "@" . $_domain))) {
Log::trace(__FUNCTION__ . ": An entry with address " . $local . "@" . $_domain . " was found.");
if (!empty($postdata['id'])) {
$user_found_dn = key($user_found);
$user_found_unique_attr = $this->unique_attribute_value($user_found_dn);
if ($user_found_unique_attr == $postdata['id']) {
Log::trace(__FUNCTION__ . ": Entry with address " . $local . "@" . $_domain . " is actually us.");
break;
}
} // empty($postdata['id'])
// Otherwise this is a new user and therefore the entry found with
// this address is definitely not us
$local = $_local . $x;
$x++;
}
$secondary_mail_addresses[] = $local . "@" . $_domain;
}
if (($key = array_search($postdata['mail'], $secondary_mail_addresses)) !== false) {
Log::trace("Found primary mail as part of the secondary mail addresses");
unset($secondary_mail_addresses[$key]);
}
return array_unique($secondary_mail_addresses);
}
}
private function generate_uid($postdata, $attribs = array())
{
if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['uid'])) {
// Use Data Please
foreach ($attribs['auto_form_fields']['uid']['data'] as $key) {
if (!isset($postdata[$key])) {
throw new Exception("Key not set: " . $key, 12356);
}
}
if (empty($postdata['uid'])) {
$postdata['uid'] = $postdata['sn'];
}
$userdata = kolab_recipient_policy::normalize_userdata($postdata);
$uid = kolab_recipient_policy::uid($userdata);
Log::debug("uid from recipient policy: " . var_export($uid, TRUE));
$orig_uid = $uid;
$auth = Auth::get_instance($_SESSION['user']->get_domain());
$x = 2;
while (($user_found = $auth->user_find_by_attribute(array('uid' => $uid)))) {
if (!empty($postdata['id'])) {
$user_found_dn = key($user_found);
$user_found_unique_attr = $this->unique_attribute_value($user_found_dn);
//console("user with uid $uid found", $user_found_unique_attr);
if ($user_found_unique_attr == $postdata['id']) {
//console("that's us.");
break;
}
}
$uid = $orig_uid . $x;
$x++;
}
return $uid;
}
}
private function generate_uidnumber($postdata, $attribs = array())
{
if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['uidnumber'])) {
$auth = Auth::get_instance($_SESSION['user']->get_domain());
$conf = Conf::get_instance();
// users search parameters
$params = array('page_size' => 200);
$search = array(
'operator' => 'AND',
'params' => array(
'objectclass' => array(
'type' => 'exact',
'value' => 'posixaccount',
),
'uidnumber' => array(
'type' => '>=',
),
),
);
$highest_uidnumber = $conf->get('uidnumber_lower_barrier');
if (!$highest_uidnumber) {
$highest_uidnumber = 999;
}
do {
$search['params']['uidnumber']['value'] = $highest_uidnumber;
$users = $auth->list_users(NULL, array('uidnumber'), $search, $params);
foreach ($users['list'] as $dn => $attributes) {
if (!array_key_exists('uidnumber', $attributes)) {
continue;
}
if ($attributes['uidnumber'] > $highest_uidnumber) {
$highest_uidnumber = $attributes['uidnumber'];
}
}
}
while ($users['count'] == $params['page_size']);
$uidnumber = ($highest_uidnumber + 1);
$postdata['uidnumber'] = $uidnumber;
if (empty($postdata['gidnumber'])) {
$gidnumber = $this->generate_gidnumber($postdata, $attribs);
$uidnumber = max($uidnumber, $gidnumber);
}
return $uidnumber;
}
}
private function list_options_kolabdelegate($postdata, $attribs = array())
{
// return specified records only, by exact DN attributes
if (!empty($postdata['list'])) {
$data['page_size'] = count($postdata['list']);
$data['search'] = array(
'entrydn' => array(
'value' => $postdata['list'],
'type' => 'exact',
),
);
}
// return records with specified string
else {
$keyword = array('value' => $postdata['search']);
$data['page_size'] = 15;
$data['search'] = array(
'displayname' => $keyword,
'cn' => $keyword,
'mail' => $keyword,
);
}
$data['attributes'] = array('displayname', 'mail');
$service = $this->controller->get_service('users');
$result = $service->users_list(null, $data);
$list = $result['list'];
// convert to key=>value array
foreach ($list as $idx => $value) {
$list[$idx] = $value['displayname'];
if (!empty($value['mail'])) {
$list[$idx] .= ' <' . $value['mail'] . '>';
}
}
return $list;
}
private function list_options_member($postdata, $attribs = array())
{
return $this->_list_options_members($postdata, $attribs);
}
private function list_options_nsrole($postdata, $attribs = array())
{
//console("Listing options for attribute 'nsrole', while the expected attribute to use is 'nsroledn'");
return $this->list_options_nsroledn($postdata, $attribs);
}
private function list_options_nsroledn($postdata, $attribs = Array())
{
// return specified records only, by exact DN attributes
if (!empty($postdata['list'])) {
$data['page_size'] = count($postdata['list']);
$data['search'] = array(
'entrydn' => array(
'value' => $postdata['list'],
'type' => 'exact',
),
);
}
// return records with specified string
else {
$keyword = array('value' => $postdata['search']);
$data['page_size'] = 15;
$data['search'] = array(
'displayname' => $keyword,
'cn' => $keyword,
'mail' => $keyword,
);
}
$data['attributes'] = array('cn');
$service = $this->controller->get_service('roles');
$result = $service->roles_list(null, $data);
$list = $result['list'];
// convert to key=>value array
foreach ($list as $idx => $value) {
$list[$idx] = $value['cn'];
}
return $list;
}
private function list_options_uniquemember($postdata, $attribs = array())
{
$result = $this->_list_options_members($postdata, $attribs);
return $result;
}
private function list_options_uniquemember_resource($postdata, $attribs = array())
{
return $this->_list_options_resources($postdata, $attribs);
}
private function select_options_c($postdata, $attribs = array())
{
return $this->_select_options_from_db('c');
}
private function select_options_objectclass($postdata, $attribs = array())
{
$auth = Auth::get_instance();
$list = $auth->schema_classes();
if (is_array($list)) {
sort($list);
}
return array('list' => $list);
}
private function select_options_attribute($postdata, $attribs = array())
{
$auth = Auth::get_instance();
$list = $auth->schema_attributes($postdata['classes']);
if (is_array($list['may'])) {
// return required + optional
if (is_array($list['must']) && !empty($list['must'])) {
$list['may'] = array_unique(array_merge($list['may'], $list['must']));
}
sort($list['may']);
}
return array(
'list' => $list['may'],
'required' => $list['must']
);
}
private function select_options_ou($postdata, $attribs = array())
{
$auth = Auth::get_instance();
$conf = Conf::get_instance();
$unique_attr = $this->unique_attribute();
$object_type = $postdata['object_type'];
$object_key = $postdata['type_key'];
$base_dn = $auth->subject_base_dn($object_key, $object_type);
if (!empty($postdata['id'])) {
$subjects = $auth->search($base_dn, '(' . $unique_attr . '=' . $postdata['id'] . ')')->entries(true);
if ($subjects) {
$subject = array_shift($subjects);
$subject_dn = key($subject);
$subject_dn_components = ldap_explode_dn($subject_dn, 0);
if ($subject_dn_components) {
unset($subject_dn_components['count']);
array_shift($subject_dn_components);
$default = strtolower(implode(',', $subject_dn_components));
}
}
}
if (empty($default)) {
$default = $base_dn;
}
$_ous = array();
if ($ous = $auth->search($base_dn, '(objectclass=organizationalunit)')) {
foreach ($ous->entries(true) as $ou_dn => $ou_attrs) {
$_ous[] = strtolower($ou_dn);
}
sort($_ous);
}
return array(
'list' => $_ous,
'default' => strtolower($default),
);
}
private function select_options_preferredlanguage($postdata, $attribs = array())
{
$options = $this->_select_options_from_db('preferredlanguage');
$conf = Conf::get_instance();
$default = $conf->get('default_locale');
if (!$default) {
$default = 'en_US';
}
if (!empty($postdata['preferredlanguage'])) {
$default = $postdata['preferredlanguage'];
}
$options['default'] = $default;
return $options;
}
/**
* Checks if specified list of email addresses is already
* in use by another user
*/
private function _email_addresses_in_use($addresses, $attr_name, $postdata)
{
$auth = Auth::get_instance();
foreach ($addresses as $addr) {
if ($users = $auth->find_recipient($addr)) {
Log::trace(__FUNCTION__ . ": An entry with address $addr was found.");
if (!empty($postdata['id']) && count($users) == 1) {
$user_found_dn = key($users);
$user_found_unique_attr = $this->unique_attribute_value($user_found_dn);
if ($user_found_unique_attr == $postdata['id']) {
// check if the address is in another field, we prevent here
// from e.g. adding primary mail address into aliases list
$found = false;
$user = $users[$user_found_dn];
unset($user[$attr_name]);
foreach ($user as $attr => $list) {
- if (in_array($addr, (array) $list)) {
- $found = true;
- break;
+ foreach ((array) $list as $email) {
+ if (compare_email($addr, $email)) {
+ $found = true;
+ break 2;
+ }
}
}
if (!$found) {
Log::trace(__FUNCTION__ . ": Entry with address $addr is actually us.");
continue;
}
// @TODO: throw different exception?
}
}
throw new Exception("Email address '$addr' is already in use", 694);
}
}
}
private function validate_alias($value, $postdata = null, $validation_type = null)
{
$conf = Conf::get_instance();
if (!is_array($value)) {
$value = (array)($value);
}
foreach ($value as $mail_address) {
if (!$this->_validate_email_address($mail_address)) {
throw new Exception("Invalid email address '$mail_address'", 692);
}
if ($validation_type == self::VALIDATE_BASIC) {
continue;
}
// Only validate the 'alias' attribute is in any of my domain name
// spaces if indeed it is listed as a mail attribute.
if (in_array('alias', $conf->get_list('mail_attributes'))) {
if (!$this->_validate_email_address_in_any_of_my_domains($mail_address)) {
throw new Exception("Email address '$mail_address' not in local domain", 693);
}
}
}
// Check if addresses are not already in use
if ($validation_type == self::VALIDATE_EXTENDED) {
$this->_email_addresses_in_use($value, 'alias', $postdata);
}
return 'OK';
}
private function validate_associateddomain($value, $postdata = array(), $validation_type = null)
{
if (!is_array($value)) {
$value = (array)($value);
}
//console("form_value.validate_associateddomain(\$value)", $value);
return $value;
}
private function validate_astaccountrealmedpassword($value, $postdata = array(), $validation_type = null)
{
if (!array_key_exists('userpassword', $postdata) || empty($postdata['userpassword'])) {
return $value;
}
if (!array_key_exists('uid', $postdata) || empty($postdata['uid'])) {
$postdata['uid'] = $this->generate_uid($postdata);
}
$str = $postdata['uid'] . ":" . $_SESSION['user']->get_domain() . ":" . $postdata['userpassword'];
Log::trace("Inserting astaccountrealmedpassword with value md5('" . $str . "');");
return md5($str);
}
private function validate_mail($value, $postdata = array(), $validation_type = null)
{
$conf = Conf::get_instance();
if (!is_array($value)) {
$value = (array)($value);
}
foreach ($value as $mail_address) {
if (!$this->_validate_email_address($mail_address)) {
throw new Exception("Invalid email address '$mail_address'", 692);
}
if ($validation_type == self::VALIDATE_BASIC) {
continue;
}
// Only validate the 'mail' attribute is in any of my domain name
// spaces if indeed it is listed as a mail attribute.
if (in_array('mail', $conf->get_list('mail_attributes'))) {
if (!$this->_validate_email_address_in_any_of_my_domains($mail_address)) {
throw new Exception("Email address '$mail_address' not in local domain", 693);
}
}
}
return 'OK';
}
private function validate_mailquota($value, $postdata = array(), $validation_type = null)
{
// convert MB/GB into KB
if (preg_match('/^([0-9]+)\s*(KB|MB|GB)$/i', $value, $m)) {
switch (strtoupper($m[2])) {
case 'KB': $value = $m[1]; break;
case 'MB': $value = $m[1] * 1024; break;
case 'GB': $value = $m[1] * 1024 * 1024; break;
}
}
return (string) intval($value);
}
private function validate_mailalternateaddress($value, $postdata = array(), $validation_type = null)
{
$conf = Conf::get_instance();
if (!is_array($value)) {
$value = (array)($value);
}
foreach ($value as $mail_address) {
if (!$this->_validate_email_address($mail_address)) {
throw new Exception("Invalid email address '$mail_address'", 692);
}
if ($validation_type == self::VALIDATE_BASIC) {
continue;
}
// Only validate the 'mailalternateaddress' attribute is in any of my domain name
// spaces if indeed it is listed as a mail attribute.
if (in_array('mailalternateaddress', $conf->get_list('mail_attributes'))) {
if (!$this->_validate_email_address_in_any_of_my_domains($mail_address)) {
throw new Exception("Email address '$mail_address' not in local domain", 693);
}
}
}
return 'OK';
}
private function _list_options_members($postdata, $attribs = array())
{
// return specified records only, by exact DN attributes
if (!empty($postdata['list'])) {
$data['page_size'] = count($postdata['list']);
$data['search'] = array(
'params' => array(
'entrydn' => array(
'value' => $postdata['list'],
'type' => 'exact',
),
),
'operator' => 'OR'
);
}
// return records with specified string
else {
$keyword = array('value' => $postdata['search'], 'type' => 'both');
$data['page_size'] = 15;
$data['search'] = array(
'params' => array(
'displayname' => $keyword,
'cn' => $keyword,
'mail' => $keyword,
),
'operator' => 'OR'
);
}
$data['attributes'] = array('displayname', 'cn', 'mail');
$service = $this->controller->get_service('users');
$result = $service->users_list(null, $data);
$list = $result['list'];
// skip groups listing if the list if already full,
// and full list was requested (no paging)
if (empty($keyword) && $result['count'] < $data['page_size']) {
$data['attributes'] = array('cn', 'mail');
$service = $this->controller->get_service('groups');
$result = $service->groups_list(null, $data);
$list = array_merge($list, $result['list']);
}
// convert to key=>value array
foreach ($list as $idx => $value) {
if (!empty($value['displayname'])) {
$list[$idx] = $value['displayname'];
} elseif (!empty($value['cn'])) {
$list[$idx] = $value['cn'];
} else {
//console("No display name or cn for $idx");
}
if (!empty($value['mail'])) {
$list[$idx] .= ' <' . $value['mail'] . '>';
}
}
// Sort and slice
asort($list);
if (!empty($data['page_size'])) {
$list = array_slice($list, 0, $data['page_size']);
}
return $list;
}
private function _list_options_resources($postdata, $attribs = array())
{
// return specified records only, by exact DN attributes
if (!empty($postdata['list'])) {
$data['page_size'] = count($postdata['list']);
$data['search'] = array(
'entrydn' => array(
'value' => $postdata['list'],
'type' => 'exact',
),
);
}
// return records with specified string
else {
$keyword = array('value' => $postdata['search']);
$data['page_size'] = 15;
$data['search'] = array(
'cn' => $keyword,
);
}
$data['attributes'] = array('cn');
//console("api/form_value._list_options_resources() searching with data", $data);
$service = $this->controller->get_service('resources');
$result = $service->resources_list(null, $data);
$list = $result['list'];
// convert to key=>value array
foreach ($list as $idx => $value) {
if (!empty($value['displayname'])) {
$list[$idx] = $value['displayname'];
} elseif (!empty($value['cn'])) {
$list[$idx] = $value['cn'];
} else {
//console("No display name or cn for $idx");
}
}
return $list;
}
private function _select_options_from_db($attribute)
{
if (empty($attribute)) {
return false;
}
$db = SQL::get_instance();
$result = $db->fetch_assoc($db->query("SELECT option_values FROM options WHERE attribute = ?", $attribute));
$result = json_decode($result['option_values']);
return array('list' => $result);
}
private function _validate_email_address($mail_address)
{
$valid = true;
$at_index = strrpos($mail_address, "@");
if (is_bool($at_index) && !$at_index) {
$valid = false;
} else {
$domain = substr($mail_address, $at_index+1);
$local = substr($mail_address, 0, $at_index);
if (strlen($local) < 1 || strlen($local) > 64) {
// local part length exceeded
//console("Local part of email address is longer than permitted");
$valid = false;
} else if (strlen($domain) < 1 || strlen($domain) > 255) {
// domain part length exceeded
//console("Domain part of email address is longer than permitted");
$valid = false;
} else if ($local[0] == '.' || $local[strlen($local)-1] == '.') {
// local part starts or ends with '.'
//console("Local part of email address starts or ends with '.'");
$valid = false;
} else if (preg_match('/\\.\\./', $local)) {
//console("Local part contains two consecutive dots");
// local part has two consecutive dots
$valid = false;
} else if (!preg_match('/^[A-Za-z0-9\\-\\.]+$/', $domain)) {
// character not valid in domain part
//console("Invalid character in domain part");
$valid = false;
} else if (preg_match('/\\.\\./', $domain)) {
// domain part has two consecutive dots
//console("Domain part contains two consecutive dots");
$valid = false;
} else if (!preg_match('/^(\\\\.|[A-Za-z0-9!#%&`_=\\/$\'*+?^{}|~.-])+$/', str_replace("\\\\","",$local))) {
// character not valid in local part unless
// local part is quoted
if (!preg_match('/^"(\\\\"|[^"])+"$/', str_replace("\\\\","",$local))) {
//console("Unquoted invalid character in local part");
$valid = false;
}
}
// if ($valid && !(checkdnsrr($domain,"MX") || checkdnsrr($domain,"A"))) {
// // domain not found in DNS
// $valid = false;
// }
}
return $valid;
}
private function _validate_email_address_in_any_of_my_domains($mail_address)
{
$at_index = strrpos($mail_address, "@");
if (is_bool($at_index) && !$at_index) {
throw new Exception("Invalid email address: No domain name space", 235);
} else {
$email_domain = substr($mail_address, $at_index+1);
}
$my_primary_domain = $_SESSION['user']->get_domain();
if ($email_domain == $my_primary_domain) {
return true;
}
$auth = Auth::get_instance();
$conf = Conf::get_instance();
$all_domains = $auth->list_domains();
$all_domains = $all_domains['list'];
$valid_domains = array();
$dna = $conf->get('domain_name_attribute');
$valid = false;
Log::trace("_validate_email_address_in_any_of_mydomains(\$mail_address = " . var_export($mail_address, TRUE) . ")");
Log::trace("\$all_domains includes: " . var_export($all_domains, TRUE) . " (must include domain for \$mail_address)");
foreach ($all_domains as $domain_id => $domain_attrs) {
if (!is_array($domain_attrs[$dna])) {
$domain_attrs[$dna] = (array)($domain_attrs[$dna]);
}
if (in_array($my_primary_domain, $domain_attrs[$dna])) {
$valid_domains = array_merge($valid_domains, $domain_attrs[$dna]);
}
}
if (in_array($email_domain, $valid_domains)) {
$valid = true;
}
if ($valid) {
Log::trace("Found email address to be in one of my domains.");
} else {
Log::trace("Found email address to NOT be in one of my domains.");
}
return $valid;
}
}
diff --git a/lib/functions.php b/lib/functions.php
index 016f1f3..fd5aa4a 100644
--- a/lib/functions.php
+++ b/lib/functions.php
@@ -1,133 +1,151 @@
<?php
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab Web Admin Panel |
| |
| Copyright (C) 2011-2012, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
| Author: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
// Initialization and basic functions
// application constants
define('KADM_START', microtime(true));
define('KADM_VERSION', '0.1');
define('KADM_CHARSET', 'utf-8');
define('INSTALL_PATH', dirname(__FILE__));
// Check critical PHP settings here.
$crit_opts = array(
'mbstring.func_overload' => 0,
'magic_quotes_runtime' => 0,
);
foreach ($crit_opts as $optname => $optval) {
if ($optval != ini_get($optname)) {
die("ERROR: Wrong '$optname' option value!");
}
}
$include_path = INSTALL_PATH . PATH_SEPARATOR;
$include_path .= INSTALL_PATH . '/client' . PATH_SEPARATOR;
$include_path .= INSTALL_PATH . '/api' . PATH_SEPARATOR;
$include_path .= INSTALL_PATH . '/ext' . PATH_SEPARATOR;
$include_path .= ini_get('include_path');
if (set_include_path($include_path) === false) {
die("Fatal error: ini_set/set_include_path does not work.");
}
ini_set('error_reporting', E_ALL &~ E_NOTICE &~ E_STRICT);
ini_set('error_log', INSTALL_PATH . '/../logs/errors');
// Set internal charset
mb_internal_encoding(KADM_CHARSET);
@mb_regex_encoding(KADM_CHARSET);
// register autoloader
function class_autoloader($classname)
{
$classname = preg_replace('/(Net|HTTP)_(.+)/', "\\1/\\2", $classname);
if ($fp = @fopen("$classname.php", 'r', true)) {
include_once "$classname.php";
fclose($fp);
return true;
}
return false;
}
spl_autoload_register('class_autoloader');
/**
* Prints debug info into the 'console' log
*/
function console()
{
$args = func_get_args();
$msg = array();
foreach ($args as $arg) {
$msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
}
write_log('console', join(";\n", $msg));
}
/**
* Appends a line to a log file in the logs directory.
* Date will be added automatically to the line.
*
* @param string $name Name of the log file
* @param mixed $line Line to append
*/
function write_log($name, $line)
{
if (!is_string($line)) {
$line = var_export($line, true);
}
$log_dir = dirname(__FILE__) . '/../logs';
$logfile = $log_dir . '/' . $name;
$date = date('d-M-Y H:i:s O');
$sess_id = session_id();
$logline = sprintf("[%s]%s: %s\n", $date, $sess_id ? "($sess_id)" : '', $line);
if ($fp = @fopen($logfile, 'a')) {
fwrite($fp, $logline);
fflush($fp);
fclose($fp);
return;
}
if ($name == 'errors') {
// send error to PHPs error handler if write to file didn't succeed
trigger_error($line, E_USER_ERROR);
}
}
function timer($time = null, $label = '')
{
$now = microtime(true);
if ($time) {
console(($label ? $label.' ' : '') . sprintf('%.4f', $now - $time));
}
return $now;
}
+
+/**
+ * Compare two email addresses with correct character-case handling
+ * i.e. local part is case-sensitive, domain part is not
+ */
+function compare_email($email1, $email2)
+{
+ $email1 = explode('@', $email1);
+ $email2 = explode('@', $email2);
+
+ $domain1 = array_pop($email1);
+ $domain2 = array_pop($email2);
+
+ $email1 = implode('@', $email1) . '@' . mb_strtolower($domain1);
+ $email2 = implode('@', $email2) . '@' . mb_strtolower($domain2);
+
+ return $email1 === $email2;
+}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Apr 4, 5:07 AM (1 d, 22 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18822659
Default Alt Text
(115 KB)

Event Timeline