1<?php 2use \LAM\TYPES\TypeManager; 3use function LAM\TYPES\getScopeFromTypeId; 4use LAM\TYPES\ConfiguredType; 5/* 6 7This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) 8Copyright (C) 2003 - 2006 Tilo Lutz 9 2007 - 2020 Roland Gruber 10 11This program is free software; you can redistribute it and/or modify 12it under the terms of the GNU General Public License as published by 13the Free Software Foundation; either version 2 of the License, or 14(at your option) any later version. 15 16This program is distributed in the hope that it will be useful, 17but WITHOUT ANY WARRANTY; without even the implied warranty of 18MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19GNU General Public License for more details. 20 21You should have received a copy of the GNU General Public License 22along with this program; if not, write to the Free Software 23Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 24*/ 25 26 27/** 28* Manages Unix accounts for groups. 29* 30* @package modules 31* 32* @author Tilo Lutz 33* @author Roland Gruber 34* @author Michael Duergner 35*/ 36 37/** 38* Manages the object class "posixGroup" for groups. 39* 40* @package modules 41*/ 42class posixGroup extends baseModule implements passwordService { 43 44 /** change GIDs of users and hosts? */ 45 private $changegids; 46 /** password attribute */ 47 protected $passwordAttrName = 'userPassword'; 48 /** cache for existing GID numbers */ 49 private $cachedGIDList = null; 50 /** cache for existing users and their GIDs */ 51 private $cachedUserToGIDList = null; 52 /** cache for existing groups */ 53 private $cachedGroupNameList = null; 54 55 /** 56 * {@inheritDoc} 57 * @see baseModule::getManagedAttributes() 58 */ 59 function get_uploadColumns($selectedModules, &$type) { 60 $return = parent::get_uploadColumns($selectedModules, $type); 61 $typeId = $type->getId(); 62 if ($this->manageCnAndDescription($selectedModules)) { 63 array_unshift($return, 64 array( 65 'name' => 'posixGroup_cn', 66 'description' => _('Group name'), 67 'help' => 'cn', 68 'example' => _('adminstrators'), 69 'required' => true, 70 'unique' => true 71 ) 72 ); 73 array_unshift($return, 74 array( 75 'name' => 'posixGroup_description', 76 'description' => _('Group description'), 77 'help' => 'description', 78 'example' => _('Administrators group') 79 ) 80 ); 81 } 82 if (!$this->isBooleanConfigOptionSet('posixGroup_' . $typeId . '_hidememberUid')) { 83 $return[] = array( 84 'name' => 'posixGroup_members', 85 'description' => _('Group members'), 86 'help' => 'upload_members', 87 'example' => _('user01,user02,user03') 88 ); 89 } 90 return $return; 91 } 92 93 /** 94 * {@inheritDoc} 95 * @see baseModule::build_uploadAccounts() 96 */ 97 function build_uploadAccounts($rawAccounts, $ids, &$partialAccounts, $selectedModules, &$type) { 98 $error_messages = array(); 99 $needAutoGID = array(); 100 $typeId = $type->getId(); 101 for ($i = 0; $i < sizeof($rawAccounts); $i++) { 102 if (!in_array("posixGroup", $partialAccounts[$i]['objectClass'])) $partialAccounts[$i]['objectClass'][] = "posixGroup"; 103 if ($this->manageCnAndDescription($selectedModules)) { 104 // group name 105 $this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'posixGroup_cn', 'cn', 'groupname', $this->messages['cn'][3], $error_messages); 106 } 107 // GID 108 if ($rawAccounts[$i][$ids['posixGroup_gid']] == "") { 109 // autoGID 110 $needAutoGID[] = $i; 111 } 112 elseif (get_preg($rawAccounts[$i][$ids['posixGroup_gid']], 'digit')) { 113 $partialAccounts[$i]['gidNumber'] = $rawAccounts[$i][$ids['posixGroup_gid']]; 114 } 115 else { 116 $errMsg = $this->messages['gidNumber'][8]; 117 array_push($errMsg, array($i)); 118 $error_messages[] = $errMsg; 119 } 120 if ($this->manageCnAndDescription($selectedModules)) { 121 // description (UTF-8, no regex check needed) 122 if ($rawAccounts[$i][$ids['posixGroup_description']] == "") { 123 $partialAccounts[$i]['description'] = $partialAccounts[$i]['cn']; 124 } 125 else { 126 $partialAccounts[$i]['description'] = $rawAccounts[$i][$ids['posixGroup_description']]; 127 } 128 } 129 // group members 130 if (!$this->isBooleanConfigOptionSet('posixGroup_' . $typeId . '_hidememberUid') && ($rawAccounts[$i][$ids['posixGroup_members']] != "")) { 131 if (get_preg($rawAccounts[$i][$ids['posixGroup_members']], 'usernameList')) { 132 $partialAccounts[$i]['memberUid'] = explode(",", $rawAccounts[$i][$ids['posixGroup_members']]); 133 } 134 else { 135 $errMsg = $this->messages['memberUID'][0]; 136 array_push($errMsg, $i); 137 $error_messages[] =$errMsg; 138 } 139 } 140 // password 141 if ($rawAccounts[$i][$ids['posixGroup_password']] != "") { 142 if (get_preg($rawAccounts[$i][$ids['posixGroup_password']], 'password')) { 143 $partialAccounts[$i][$this->passwordAttrName] = pwd_hash($rawAccounts[$i][$ids['posixGroup_password']], true, $this->moduleSettings['posixAccount_pwdHash'][0]); 144 } 145 else { 146 $error_messages[] = $this->messages['userPassword'][1]; 147 } 148 } 149 } 150 // fill in autoGIDs 151 if (sizeof($needAutoGID) > 0) { 152 $errorsTemp = array(); 153 $gids = $this->getNextGIDs(sizeof($needAutoGID), $errorsTemp, $type); 154 if (is_array($gids)) { 155 for ($i = 0; $i < sizeof($needAutoGID); $i++) { 156 $partialAccounts[$i]['gidNumber'] = $gids[$i]; 157 } 158 } 159 else { 160 $error_messages[] = $this->messages['gidNumber'][2]; 161 } 162 } 163 return $error_messages; 164 } 165 166 /** 167 * Checks if the group which should be deleted is still used as primary group. 168 * 169 * @return List of LDAP operations, same as for save_attributes() 170 */ 171 function delete_attributes() { 172 $return = array(); 173 $result = searchLDAPByFilter('(&(objectClass=posixAccount)(gidNumber=' . $this->attributes['gidNumber'][0] . '))', array('dn'), array('user', 'host')); 174 if (sizeof($result) > 0) { 175 $max = 5; 176 if (sizeof($result) < 5) { 177 $max = sizeof($result); 178 } 179 $users = array(); 180 for ($i = 0; $i < $max; $i++) { 181 $users[] = getAbstractDN($result[$i]['dn']); 182 } 183 $message = $this->messages['primaryGroup'][0]; 184 $message[] = implode(', ', $users); 185 $return[$this->getAccountContainer()->dn_orig]['errors'][] = $message; 186 } 187 return $return; 188 } 189 190 /** 191 * Returns the HTML meta data for the main account page. 192 * 193 * @return array HTML meta data 194 * 195 * @see baseModule::get_metaData() 196 */ 197 function display_html_attributes() { 198 $return = new htmlResponsiveRow(); 199 $modules = $this->getAccountContainer()->get_type()->getModules(); 200 $typeId = $this->getAccountContainer()->get_type()->getId(); 201 if ($this->autoAddObjectClasses || (isset($this->attributes['objectClass']) && in_array('posixGroup', $this->attributes['objectClass']))) { 202 // auto sync group members 203 if ($this->isBooleanConfigOptionSet('posixGroup_autoSyncGon')) { 204 $this->syncGon(true); 205 } 206 // group name 207 if ($this->manageCnAndDescription($modules)) { 208 $this->addSimpleInputTextField($return, 'cn', _("Group name"), true); 209 } 210 // GID number 211 $gidNumberInput = $this->addSimpleInputTextField($return, 'gidNumber', _('GID number')); 212 $gidNumberInput->setValidationRule(htmlElement::VALIDATE_NUMERIC); 213 // description 214 if ($this->manageCnAndDescription($modules)) { 215 $this->addSimpleInputTextField($return, 'description', _('Description')); 216 } 217 // password buttons 218 if (checkIfWriteAccessIsAllowed($this->get_scope()) && isset($this->attributes[$this->passwordAttrName][0])) { 219 $return->addLabel(new htmlOutputText(_('Password'))); 220 $pwdContainer = new htmlGroup(); 221 if (pwd_is_enabled($this->attributes[$this->passwordAttrName][0])) { 222 $pwdContainer->addElement(new htmlButton('lockPassword', _('Lock password'))); 223 } 224 else { 225 $pwdContainer->addElement(new htmlButton('unlockPassword', _('Unlock password'))); 226 } 227 $pwdContainer->addElement(new htmlSpacer('0.5rem', null)); 228 $pwdContainer->addElement(new htmlButton('removePassword', _('Remove password'))); 229 $return->addField($pwdContainer); 230 } 231 if (isset($this->orig['gidNumber'][0]) && $this->attributes['gidNumber'][0]!=$this->orig['gidNumber'][0]) { 232 $return->add(new htmlResponsiveInputCheckbox('changegids', $this->changegids, _('Change GID number of users and hosts'), 'changegids'), 12); 233 } 234 // group members 235 if (!$this->isBooleanConfigOptionSet('posixGroup_' . $typeId . '_hidememberUid')) { 236 $return->addVerticalSpacer('0.5rem'); 237 $return->addLabel(new htmlOutputText(_("Group members"))); 238 $membersGroup = new htmlGroup(); 239 if (!$this->isBooleanConfigOptionSet('posixGroup_autoSyncGon')) { 240 $membersGroup->addElement(new htmlAccountPageButton(get_class($this), 'user', 'open', _('Edit members'))); 241 } 242 $membersGroup->addElement(new htmlHelpLink('members')); 243 $return->addField($membersGroup); 244 $return->addLabel(new htmlOutputText(' ', false)); 245 $users = $this->getUsers(); 246 $members = array(); 247 if (isset($this->attributes['memberUid'][0])) { 248 foreach ($this->attributes['memberUid'] as $uid) { 249 if (isset($users[$uid]) && isset($users[$uid]['cn'])) { 250 $members[] = $uid . ' (' . $users[$uid]['cn'] . ')'; 251 } 252 else { 253 $members[] = $uid; 254 } 255 } 256 } 257 $members = array_unique($members); 258 natcasesort($members); 259 $members = array_map('htmlspecialchars', $members); 260 $return->addField(new htmlOutputText(implode('<br>', $members), false)); 261 } 262 // remove button 263 if (!$this->autoAddObjectClasses) { 264 $return->addVerticalSpacer('2rem'); 265 $remButton = new htmlButton('remObjectClass', _('Remove Unix extension')); 266 $return->add($remButton, 12, 12, 12, 'text-center'); 267 } 268 } 269 else { 270 // add button 271 $return->add(new htmlButton('addObjectClass', _('Add Unix extension')), 12); 272 } 273 return $return; 274 } 275 276 277 /** 278 * Displays selections to add or remove users from current group. 279 * 280 * @return array meta HTML output 281 */ 282 function display_html_user() { 283 $return = new htmlResponsiveRow(); 284 if (!isset($this->attributes['memberUid'])) { 285 $this->attributes['memberUid'] = array(); 286 } 287 // load list with all users 288 $userAndGIDs = $this->getUsers(); 289 $users = array(); 290 foreach ($userAndGIDs as $user => $userAttrs) { 291 if (!in_array($user, $this->attributes['memberUid'])) { 292 $display = $user . ' (' . $userAttrs['cn'] . ')'; 293 if ($this->attributes['gidNumber'][0] == $userAttrs['gid']) { 294 if (isset($this->moduleSettings['posixAccount_primaryGroupAsSecondary'][0]) 295 && ($this->moduleSettings['posixAccount_primaryGroupAsSecondary'][0] == 'true')) { 296 $users[$display] = $user; 297 } 298 } 299 else { 300 $users[$display] = $user; 301 } 302 } 303 } 304 natcasesort($users); 305 $return->add(new htmlSubTitle(_("Group members")), 12); 306 307 $remUsers = array(); 308 if (isset($this->attributes['memberUid'])) { 309 $remUsers = $this->attributes['memberUid']; 310 } 311 natcasesort($remUsers); 312 $remUsersDescriptive = array(); 313 foreach ($remUsers as $user) { 314 if (isset($userAndGIDs[$user])) { 315 $remUsersDescriptive[$user . ' (' . $userAndGIDs[$user]['cn'] . ')'] = $user; 316 } 317 else { 318 $remUsersDescriptive[$user] = $user; 319 } 320 } 321 322 $this->addDoubleSelectionArea($return, _("Selected users"), _("Available users"), $remUsersDescriptive, null, 323 $users, null, 'members', false, true); 324 325 // sync from group of names 326 $gon = $this->getAccountContainer()->getAccountModule('groupOfNames'); 327 if ($gon == null) { 328 $gon = $this->getAccountContainer()->getAccountModule('groupOfUniqueNames'); 329 } 330 if ($gon == null) { 331 $gon = $this->getAccountContainer()->getAccountModule('groupOfMembers'); 332 } 333 if ($gon != null) { 334 $return->addVerticalSpacer('2rem'); 335 $syncButton = new htmlButton('syncGON', sprintf(_('Sync from %s'), $gon->get_alias())); 336 $syncButton->setIconClass('refreshButton'); 337 $return->add($syncButton, 12, 12, 12, 'text-center'); 338 $return->addVerticalSpacer('1rem'); 339 $return->add(new htmlResponsiveInputCheckbox('syncGON_delete', true, _('Delete non-matching entries'), null, false), 12); 340 } 341 $windows = $this->getAccountContainer()->getAccountModule('windowsGroup'); 342 if ($windows != null) { 343 $return->addVerticalSpacer('2rem'); 344 $syncButton = new htmlButton('syncWindows', sprintf(_('Sync from %s'), $windows->get_alias())); 345 $syncButton->setIconClass('refreshButton'); 346 $return->add($syncButton, 12, 12, 12, 'text-center'); 347 $return->addVerticalSpacer('1rem'); 348 $return->add(new htmlResponsiveInputCheckbox('syncWindows_delete', true, _('Delete non-matching entries'), null, false), 12); 349 } 350 351 // back button 352 $return->addVerticalSpacer('2rem'); 353 $return->add(new htmlAccountPageButton(get_class($this), 'attributes', 'back', _('Back')), 12); 354 355 return $return; 356 } 357 358 /** 359 * Returns true if this module can manage accounts of the current type, otherwise false. 360 * 361 * @return boolean true if module fits 362 */ 363 public function can_manage() { 364 return in_array($this->get_scope(), array('group')); 365 } 366 367 /** 368 * Returns meta data that is interpreted by parent class 369 * 370 * @return array array with meta data 371 */ 372 function get_metaData() { 373 $return = array(); 374 // icon 375 $return['icon'] = 'tux.png'; 376 if ($this->get_scope() == "group") { 377 // this is a base module 378 $return["is_base"] = true; 379 // LDAP filter 380 $return["ldap_filter"] = array('or' => "(objectClass=posixGroup)"); 381 } 382 // alias name 383 $return["alias"] = _('Unix'); 384 // RDN attribute 385 $return["RDN"] = array("cn" => "normal"); 386 // module dependencies 387 $return['dependencies'] = array('depends' => array(), 'conflicts' => array()); 388 // managed object classes 389 $return['objectClasses'] = array('posixGroup'); 390 // LDAP aliases 391 $return['LDAPaliases'] = array('commonName' => 'cn'); 392 // managed attributes 393 $return['attributes'] = array('gidNumber', $this->passwordAttrName, 'memberUid'); 394 // profile options 395 if (!$this->autoAddObjectClasses) { 396 $profileContainer = new htmlResponsiveRow(); 397 $profileContainer->add(new htmlResponsiveInputCheckbox('posixGroup_addExt', false, _('Automatically add this extension'), 'autoAdd'), 12); 398 $return['profile_options'] = $profileContainer; 399 } 400 // available PDF fields 401 $return['PDF_fields'] = array( 402 'gidNumber' => _('GID number'), 403 ); 404 // upload fields 405 $return['upload_columns'] = array( 406 array( 407 'name' => 'posixGroup_gid', 408 'description' => _('GID number'), 409 'help' => 'gidNumber', 410 'example' => '2034' 411 ), 412 array( 413 'name' => 'posixGroup_password', 414 'description' => _('Group password'), 415 'help' => 'password', 416 'example' => _('secret') 417 ) 418 ); 419 // help Entries 420 $return['help'] = array( 421 'gidNumber' => array( 422 "Headline" => _("GID number"), 'attr' => 'gidNumber', 423 "Text" => _("If empty GID number will be generated automatically depending on your configuration settings.") 424 ), 425 'description' => array( 426 "Headline" => _("Description"), 'attr' => 'description', 427 "Text" => _("Group description. If left empty group name will be used.") 428 ), 429 'members' => array( 430 "Headline" => _("Group members"), 'attr' => 'memberUid', 431 "Text" => _("Users who are member of the current group. Users who have set their primary group to this group will not be shown.") 432 ), 433 'upload_members' => array( 434 "Headline" => _("Group members"), 'attr' => 'memberUid', 435 "Text" => _("Users who will become member of the current group. User names are separated by semicolons.") 436 ), 437 'password' => array( 438 "Headline" => _("Group password"), 'attr' => $this->passwordAttrName, 439 "Text" => _("Sets the group password.") 440 ), 441 'minMaxGID' => array( 442 "Headline" => _("GID number"), 443 "Text" => _("These are the minimum and maximum numbers to use for group IDs when creating new group accounts. New group accounts will always get the highest number in use plus one.") 444 ), 445 'pwdHash' => array( 446 "Headline" => _("Password hash type"), 447 "Text" => _("LAM supports a large number of possibilities to generate the hash value of passwords. CRYPT-SHA512 and SSHA are the most common. We do not recommend to use plain text passwords unless passwords are hashed server-side.") 448 ), 449 'cn' => array( 450 "Headline" => _("Group name"), 'attr' => 'cn', 451 "Text" => _("Group name of the group which should be created. Valid characters are: a-z, A-Z, 0-9 and .-_ . If group name is already used group name will be expanded with a number. The next free number will be used.") 452 ), 453 'changegids' => array( 454 "Headline" => _("Change GID number of users and hosts"), 455 "Text" => _("The ID of this group was changed. You can update all user and host entries to the new group ID.") 456 ), 457 'gidCheckSuffix' => array ( 458 "Headline" => _("Suffix for GID/group name check"), 459 "Text" => _("LAM checks if the entered group name and GID are unique. Here you can enter the LDAP suffix that is used to search for duplicates. By default the account type suffix is used. You only need to change this if you use multiple server profiles with different OUs but need unique group names or GIDs.") 460 ), 461 'gidGenerator' => array ( 462 "Headline" => _("GID generator"), 463 "Text" => _("LAM will automatically suggest UID/GID numbers. You can either use a fixed range of numbers or an LDAP entry with object class \"sambaUnixIdPool\" or \"msSFU30DomainInfo\".") 464 . ' ' . _('Magic number will set a fixed value that must match your server configuration.') 465 ), 466 'sambaIDPoolDN' => array ( 467 "Headline" => _("Samba ID pool DN"), 468 "Text" => _("Please enter the DN of the LDAP entry with object class \"sambaUnixIdPool\".") 469 ), 470 'windowsIDPoolDN' => array ( 471 "Headline" => _("Windows domain info DN"), 472 "Text" => _("Please enter the DN of the LDAP entry with object class \"msSFU30DomainInfo\".") 473 ), 474 'filter' => array( 475 "Headline" => _("Filter"), 476 "Text" => _("Here you can enter a filter value. Only entries which contain the filter text will be shown.") 477 . ' ' . _('The filter can be any regular expression, e.g. ".*" = any characters, "^" = line start, "$" = line end.') 478 ), 479 'hidememberUid' => array( 480 "Headline" => _('Disable membership management'), 'attr' => 'memberUid', 481 "Text" => _('Disables the group membership management.') 482 ), 483 'autoAdd' => array( 484 "Headline" => _("Automatically add this extension"), 485 "Text" => _("This will enable the extension automatically if this profile is loaded.") 486 ), 487 'autoSyncGon' => array( 488 "Headline" => _("Force sync with group of names"), 489 "Text" => _("This will force syncing with group of names members of the same group.") 490 ), 491 'magicNumber' => array( 492 "Headline" => _("Magic number"), 493 "Text" => _("Please enter the magic number you configured on server side.") 494 ), 495 ); 496 497 return $return; 498 } 499 500 501 /** 502 * {@inheritDoc} 503 * @see baseModule::get_configOptions() 504 */ 505 public function get_configOptions($scopes, $allScopes) { 506 $typeManager = new TypeManager($_SESSION['conf_config']); 507 // configuration options 508 $configContainer = new htmlResponsiveRow(); 509 $configContainer->add(new htmlSubTitle(_("Groups")), 12); 510 foreach ($allScopes[get_class($this)] as $typeId) { 511 if (sizeof($allScopes[get_class($this)]) > 1) { 512 $title = new htmlDiv(null, new htmlOutputText($typeManager->getConfiguredType($typeId)->getAlias())); 513 $title->setCSSClasses(array('bold', 'responsiveLabel')); 514 $configContainer->add($title, 12, 6); 515 $configContainer->add(new htmlOutputText(' ', false), 0, 6); 516 } 517 $this->addAccountSpecificConfigOptions($configContainer, $typeId); 518 $configContainer->addVerticalSpacer('2rem'); 519 } 520 $gonModules = array('groupOfNames', 'groupOfUniqueNames', 'groupOfMembers'); 521 $gonFound = false; 522 foreach ($gonModules as $gonModule) { 523 if (!empty($allScopes[$gonModule])) { 524 foreach ($allScopes[$gonModule] as $gonTypeId) { 525 if (getScopeFromTypeId($gonTypeId) === 'group') { 526 $gonFound = true; 527 } 528 } 529 } 530 } 531 if ($gonFound || !isset($allScopes['posixAccount'])) { 532 $configContainer->add(new htmlSubTitle(_("Options")), 12); 533 } 534 if ($gonFound) { 535 $configContainer->add(new htmlResponsiveInputCheckbox('posixGroup_autoSyncGon', false, _('Force sync with group of names'), 'autoSyncGon'), 12); 536 } 537 // display password hash option only if posixAccount module is not used 538 if (!isset($allScopes['posixAccount'])) { 539 $configContainer->add(new htmlResponsiveSelect('posixAccount_pwdHash', getSupportedHashTypes(), array('CRYPT-SHA512'), _("Password hash type"), 'pwdHash'), 12); 540 } 541 return $configContainer; 542 } 543 544 /** 545 * Adds the account specific options to the config container. 546 * 547 * @param htmlResponsiveRow $configContainer container 548 * @param string $typeId type ID 549 */ 550 protected function addAccountSpecificConfigOptions(&$configContainer, $typeId) { 551 $genOptions = array( 552 _('Fixed range') => 'range', 553 _('Samba ID pool') => 'sambaPool', 554 _('Windows domain info') => 'windowsDomain', 555 _('Magic number') => 'magicNumber' 556 ); 557 $gidGeneratorSelect = new htmlResponsiveSelect('posixGroup_' . $typeId . '_gidGenerator', $genOptions, array('range'), _('GID generator'), 'gidGenerator'); 558 $gidGeneratorSelect->setHasDescriptiveElements(true); 559 $gidGeneratorSelect->setTableRowsToHide(array( 560 'range' => array('posixGroup_' . $typeId . '_sambaIDPoolDN', 'posixGroup_' . $typeId . '_windowsIDPoolDN', 'posixGroup_' . $typeId . '_magicNumber'), 561 'sambaPool' => array('posixGroup_' . $typeId . '_minGID', 'posixGroup_' . $typeId . '_maxGID', 'posixGroup_' . $typeId . '_windowsIDPoolDN', 'posixGroup_' . $typeId . '_magicNumber'), 562 'windowsDomain' => array('posixGroup_' . $typeId . '_minGID', 'posixGroup_' . $typeId . '_maxGID', 'posixGroup_' . $typeId . '_sambaIDPoolDN', 'posixGroup_' . $typeId . '_magicNumber'), 563 'magicNumber' => array('posixGroup_' . $typeId . '_minGID', 'posixGroup_' . $typeId . '_maxGID', 'posixGroup_' . $typeId . '_windowsIDPoolDN', 'posixGroup_' . $typeId . '_sambaIDPoolDN') 564 )); 565 $gidGeneratorSelect->setTableRowsToShow(array( 566 'range' => array('posixGroup_' . $typeId . '_minGID', 'posixGroup_' . $typeId . '_maxGID'), 567 'sambaPool' => array('posixGroup_' . $typeId . '_sambaIDPoolDN'), 568 'windowsDomain' => array('posixGroup_' . $typeId . '_windowsIDPoolDN'), 569 'magicNumber' => array('posixGroup_' . $typeId . '_magicNumber') 570 )); 571 $configContainer->add($gidGeneratorSelect, 12); 572 $minGidInput = new htmlResponsiveInputField(_('Minimum GID number'), 'posixGroup_' . $typeId . '_minGID', null, 'minMaxGID'); 573 $minGidInput->setRequired(true); 574 $configContainer->add($minGidInput, 12); 575 $maxGidInput = new htmlResponsiveInputField(_('Maximum GID number'), 'posixGroup_' . $typeId . '_maxGID', null, 'minMaxGID'); 576 $maxGidInput->setRequired(true); 577 $configContainer->add($maxGidInput, 12); 578 $gidGeneratorDN = new htmlResponsiveInputField(_('Samba ID pool DN'), 'posixGroup_' . $typeId . '_sambaIDPoolDN', null, 'sambaIDPoolDN'); 579 $gidGeneratorDN->setRequired(true); 580 $configContainer->add($gidGeneratorDN, 12); 581 $winGeneratorDN = new htmlResponsiveInputField(_('Windows domain info DN'), 'posixGroup_' . $typeId . '_windowsIDPoolDN', null, 'windowsIDPoolDN'); 582 $winGeneratorDN->setRequired(true); 583 $configContainer->add($winGeneratorDN, 12); 584 $magicNumber = new htmlResponsiveInputField(_('Magic number'), 'posixGroup_' . $typeId . '_magicNumber', null, 'magicNumber'); 585 $magicNumber->setRequired(true); 586 $configContainer->add($magicNumber, 12); 587 $configContainer->add(new htmlResponsiveInputField(_('Suffix for GID/group name check'), 'posixGroup_' . $typeId . '_gidCheckSuffix', '', 'gidCheckSuffix'), 12); 588 $configContainer->add(new htmlResponsiveInputCheckbox('posixGroup_' . $typeId . '_hidememberUid', false, _('Disable membership management'), 'hidememberUid'), 12); 589 } 590 591 /** 592 * {@inheritDoc} 593 * @see baseModule::check_configOptions() 594 */ 595 public function check_configOptions($typeIds, &$options) { 596 foreach ($typeIds as $typeId) { 597 if ($options['posixGroup_' . $typeId . '_gidGenerator'][0] == 'range') { 598 $this->meta['config_checks']['group']['posixGroup_' . $typeId . '_minGID'] = array ( 599 'type' => 'ext_preg', 600 'regex' => 'digit', 601 'required' => true, 602 'required_message' => $this->messages['gidNumber'][5], 603 'error_message' => $this->messages['gidNumber'][5]); 604 $this->meta['config_checks']['group']['posixGroup_' . $typeId . '_maxGID'] = array ( 605 'type' => 'ext_preg', 606 'regex' => 'digit', 607 'required' => true, 608 'required_message' => $this->messages['gidNumber'][6], 609 'error_message' => $this->messages['gidNumber'][6]); 610 $this->meta['config_checks']['group']['cmpGID'] = array ( 611 'type' => 'int_greater', 612 'cmp_name1' => 'posixGroup_' . $typeId . '_maxGID', 613 'cmp_name2' => 'posixGroup_' . $typeId . '_minGID', 614 'error_message' => $this->messages['gidNumber'][7]); 615 } 616 elseif ($options['posixGroup_' . $typeId . '_gidGenerator'][0] == 'sambaPool') { 617 $this->meta['config_checks']['group']['posixGroup_' . $typeId . '_sambaIDPoolDN'] = array ( 618 'type' => 'ext_preg', 619 'regex' => 'dn', 620 'required' => true, 621 'required_message' => $this->messages['sambaIDPoolDN'][0], 622 'error_message' => $this->messages['sambaIDPoolDN'][0]); 623 } 624 elseif ($options['posixGroup_' . $typeId . '_gidGenerator'][0] == 'windowsDomain') { 625 $this->meta['config_checks']['group']['posixGroup_' . $typeId . '_windowsIDPoolDN'] = array ( 626 'type' => 'ext_preg', 627 'regex' => 'dn', 628 'required' => true, 629 'required_message' => $this->messages['windowsIDPoolDN'][0], 630 'error_message' => $this->messages['windowsIDPoolDN'][0]); 631 } 632 elseif ($options['posixGroup_' . $typeId . '_gidGenerator'][0] == 'magicNumber') { 633 $this->meta['config_checks']['group']['posixGroup_' . $typeId . '_magicNumber'] = array ( 634 'type' => 'ext_preg', 635 'regex' => 'digit', 636 'required' => true, 637 'required_message' => $this->messages['magicNumber'][0], 638 'error_message' => $this->messages['magicNumber'][0]); 639 } 640 } 641 return parent::check_configOptions($typeIds, $options); 642 } 643 644 /** 645 * {@inheritDoc} 646 * @see baseModule::get_pdfFields() 647 */ 648 public function get_pdfFields($typeId) { 649 $fields = parent::get_pdfFields($typeId); 650 $typeManager = new TypeManager(); 651 $modules = $typeManager->getConfiguredType($typeId)->getModules(); 652 if ($this->manageCnAndDescription($modules)) { 653 $fields['cn'] = _('Group name'); 654 $fields['description'] = _('Description'); 655 } 656 if (!$this->isBooleanConfigOptionSet('posixGroup_' . $typeId . '_hidememberUid')) { 657 $fields['memberUid'] = _('Group members'); 658 $fields['memberUidPrimary'] = _('Group members (incl. primary members)'); 659 } 660 return $fields; 661 } 662 663 /** 664 * {@inheritDoc} 665 * @see baseModule::get_pdfEntries() 666 */ 667 function get_pdfEntries($pdfKeys, $typeId) { 668 $return = array(); 669 $this->addSimplePDFField($return, 'memberUid', _('Group members')); 670 $this->addSimplePDFField($return, 'cn', _('Group name')); 671 $this->addSimplePDFField($return, 'gidNumber', _('GID number')); 672 $this->addSimplePDFField($return, 'description', _('Description')); 673 if (in_array(get_class($this) . '_memberUidPrimary', $pdfKeys)) { 674 $members = !empty($this->attributes['memberUid']) ? $this->attributes['memberUid'] : array(); 675 if (!empty($this->attributes['gidNumber'])) { 676 $filter = "(&(&" . get_ldap_filter('user') . ")(gidNumber=" . $this->attributes['gidNumber'][0] . "))"; 677 $entries = searchLDAPByFilter($filter, array('uid'), array('user')); 678 foreach ($entries as $entry) { 679 $members[] = $entry['uid'][0]; 680 } 681 } 682 $this->addPDFKeyValue($return, 'memberUidPrimary', _('Group members'), $members); 683 } 684 return $return; 685 } 686 687 688 /** 689 * This function will be called when the module will be loaded 690 * 691 * @param String $base the name of the {@link accountContainer} object ($_SESSION[$base]) 692 */ 693 function init($base) { 694 // call parent init 695 parent::init($base); 696 $this->changegids=false; 697 } 698 699 700 /** 701 * This function fills the $messages variable with output messages from this module. 702 */ 703 function load_Messages() { 704 $this->messages['userPassword'][1] = array('ERROR', _('Password'), _('Password contains invalid characters. Valid characters are:') . ' a-z, A-Z, 0-9 and #*,.;:_-+!%&/|?{[()]}=@$ §°!'); 705 $this->messages['gidNumber'][0] = array('INFO', _('GID number'), _('GID number has changed. Please select checkbox to change GID number of users and hosts.')); 706 $this->messages['gidNumber'][2] = array('WARN', _('ID-Number'), _('It is possible that this ID-number is reused. This can cause several problems because files with old permissions might still exist. To avoid this warning set maxUID to a higher value.')); 707 $this->messages['gidNumber'][3] = array('ERROR', _('ID-Number'), _('No free ID-Number!')); 708 $this->messages['gidNumber'][4] = array('ERROR', _('ID-Number'), _('ID is already in use')); 709 $this->messages['gidNumber'][5] = array('ERROR', _('Minimum GID number'), _('Minimum GID number is invalid or empty!')); 710 $this->messages['gidNumber'][6] = array('ERROR', _('Maximum GID number'), _('Maximum GID number is invalid or empty!')); 711 $this->messages['gidNumber'][7] = array('ERROR', _('Maximum GID number'), _('Maximum GID number must be greater than minimum GID number!')); 712 $this->messages['gidNumber'][8] = array('ERROR', _('Account %s:') . ' posixGroup_gid', _('GID number has to be a numeric value!')); 713 $this->messages['cn'][0] = array('WARN', _('Group name'), _('You are using capital letters. This can cause problems because Windows is not case-sensitive.')); 714 $this->messages['cn'][1] = array('WARN', _('Group name'), _('Group name in use. Selected next free group name.')); 715 $this->messages['cn'][2] = array('ERROR', _('Group name'), _('Group name contains invalid characters. Valid characters are: a-z, A-Z, 0-9 and .-_ !')); 716 $this->messages['cn'][3] = array('ERROR', _('Account %s:') . ' posixGroup_cn', _('Group name contains invalid characters. Valid characters are: a-z, A-Z, 0-9 and .-_ !')); 717 $this->messages['memberUID'][0] = array('ERROR', _('Account %s:') . ' posixGroup_members', _("This value must be a list of user names separated by semicolons.")); 718 $this->messages['primaryGroup'][0] = array('ERROR', _('There are still users who have this group as their primary group.')); 719 $this->messages['sambaIDPoolDN'][0] = array('ERROR', _('Samba ID pool DN'), _('This is not a valid DN!')); 720 $this->messages['windowsIDPoolDN'][0] = array('ERROR', _('Windows domain info DN'), _('This is not a valid DN!')); 721 $this->messages['magicNumber'][0] = array('ERROR', _('Magic number'), _('Please enter a valid number.')); 722 } 723 724 725 /** 726 * {@inheritDoc} 727 * @see baseModule::getManagedAttributes() 728 */ 729 public function getManagedAttributes($typeId) { 730 $attrs = parent::getManagedAttributes($typeId); 731 $typeManager = new TypeManager(); 732 $modules = $typeManager->getConfiguredType($typeId)->getModules(); 733 if ($this->manageCnAndDescription($modules)) { 734 $attrs[] = 'cn'; 735 $attrs[] = 'description'; 736 } 737 return $attrs; 738 } 739 740 /** 741 * This functions is used to check if all settings for this module have been made. 742 * 743 * @return boolean true, if settings are complete 744 */ 745 function module_complete() { 746 if (!$this->getAccountContainer()->isNewAccount) { 747 // check if account is based on our object class 748 $objectClasses = $this->getAccountContainer()->attributes_orig['objectClass']; 749 if (is_array($objectClasses) && !in_array('posixGroup', $objectClasses)) { 750 return true; 751 } 752 } 753 $modules = $this->getAccountContainer()->get_type()->getModules(); 754 if ($this->manageCnAndDescription($modules) && ($this->attributes['cn'][0] == '')) { 755 return false; 756 } 757 if ((!isset($this->attributes['gidNumber'][0])) || $this->attributes['gidNumber'][0] === '') { 758 return false; 759 } 760 return true; 761 } 762 763 764 /** 765 * Controls if the module button the account page is visible and activated. 766 * 767 * @return string status ("enabled", "disabled", "hidden") 768 */ 769 function getButtonStatus() { 770 if (!$this->getAccountContainer()->isNewAccount) { 771 // check if account is based on our object class 772 $objectClasses = $this->getAccountContainer()->attributes_orig['objectClass']; 773 if (is_array($objectClasses) && !in_array('posixGroup', $objectClasses)) { 774 return "disabled"; 775 } 776 } 777 return "enabled"; 778 } 779 780 781 /** 782 * Processes user input of the primary module page. 783 * It checks if all input values are correct and updates the associated LDAP attributes. 784 * 785 * @return array list of info/error messages 786 */ 787 function process_attributes() { 788 $errors = array(); 789 if (isset($_POST['addObjectClass'])) { 790 if (!isset($this->attributes['objectClass'])) { 791 $this->attributes['objectClass'] = array(); 792 } 793 if (!in_array('posixGroup', $this->attributes['objectClass'])) { 794 $this->attributes['objectClass'][] = 'posixGroup'; 795 } 796 return $errors; 797 } 798 if (isset($_POST['remObjectClass'])) { 799 $this->attributes['objectClass'] = array_delete(array('posixGroup'), $this->attributes['objectClass']); 800 $attrs = $this->getManagedAttributes($this->getAccountContainer()->get_type()->getId()); 801 foreach ($attrs as $name) { 802 if (isset($this->attributes[$name])) { 803 unset($this->attributes[$name]); 804 } 805 } 806 return $errors; 807 } 808 $modules = $this->getAccountContainer()->get_type()->getModules(); 809 $typeId = $this->getAccountContainer()->get_type()->getId(); 810 // skip processing if object class is not set 811 if (!$this->autoAddObjectClasses && (!isset($this->attributes['objectClass']) || !in_array('posixGroup', $this->attributes['objectClass']))) { 812 return $errors; 813 } 814 if ($this->manageCnAndDescription($modules)) { 815 $this->attributes['description'][0] = $_POST['description']; 816 } 817 if (isset($_POST['lockPassword'])) { 818 $this->attributes[$this->passwordAttrName][0] = pwd_disable($this->attributes[$this->passwordAttrName][0]); 819 } 820 if (isset($_POST['unlockPassword'])) { 821 $this->attributes[$this->passwordAttrName][0] = pwd_enable($this->attributes[$this->passwordAttrName][0]); 822 } 823 if (isset($_POST['removePassword'])) { 824 unset($this->attributes[$this->passwordAttrName]); 825 } 826 if (isset($_POST['changegids'])) $this->changegids=true; 827 else $this->changegids=false; 828 if (!isset($this->attributes['gidNumber'][0]) || ($this->attributes['gidNumber'][0] != $_POST['gidNumber'])) { 829 // Check if GID is valid. If none value was entered, the next usable value will be inserted 830 // load min and max GID number 831 $minID = intval($this->moduleSettings['posixGroup_' . $typeId . '_minGID'][0]); 832 $maxID = intval($this->moduleSettings['posixGroup_' . $typeId . '_maxGID'][0]); 833 $this->attributes['gidNumber'][0] = $_POST['gidNumber']; 834 if ($this->attributes['gidNumber'][0] == '') { 835 // No id-number given, find free GID 836 if (!isset($this->orig['gidNumber'][0])) { 837 $newGID = $this->getNextGIDs(1, $errors, $this->getAccountContainer()->get_type()); 838 if (is_array($newGID)) { 839 $this->attributes['gidNumber'][0] = $newGID[0]; 840 } 841 else { 842 $errors[] = $this->messages['gidNumber'][3]; 843 } 844 } 845 else $this->attributes['gidNumber'][0] = $this->orig['gidNumber'][0]; 846 // old account -> return id-number which has been used 847 } 848 else { 849 $gids = $this->getGIDs($this->getAccountContainer()->get_type()); 850 // Check manual ID 851 if ($this->getAccountContainer()->isNewAccount || !isset($this->orig['gidNumber'][0]) || ($this->orig['gidNumber'][0] != $this->attributes['gidNumber'][0])) { 852 // check range 853 if ($this->moduleSettings['posixGroup_' . $typeId . '_gidGenerator'][0] == 'range') { 854 if (($this->attributes['gidNumber'][0] < $minID) || ($this->attributes['gidNumber'][0] > $maxID) || !is_numeric($this->attributes['gidNumber'][0])) { 855 $errors[] = array('ERROR', _('ID-Number'), sprintf(_('Please enter a value between %s and %s!'), $minID, $maxID)); 856 if (isset($this->orig['gidNumber'][0])) $this->attributes['gidNumber'][0] = $this->orig['gidNumber'][0]; 857 else unset($this->attributes['gidNumber'][0]); 858 } 859 } 860 // $uids is always an array but not if no entries were found 861 if (is_array($gids)) { 862 // id-number is in use and account is a new account 863 if ((in_array($this->attributes['gidNumber'][0], $gids)) && $this->orig['gidNumber'][0]=='') { 864 $errors[] = $this->messages['gidNumber'][4]; 865 unset($this->attributes['gidNumber'][0]); 866 } 867 // id-number is in use, account is existing account and id-number is not used by itself 868 if ((in_array($this->attributes['gidNumber'][0], $gids)) && $this->orig['gidNumber'][0]!='' && ($this->orig['gidNumber'][0] != $this->attributes['gidNumber'][0]) ) { 869 $errors[] = $this->messages['gidNumber'][4]; 870 $this->attributes['gidNumber'][0] = $this->orig['gidNumber'][0]; 871 } 872 } 873 } 874 } 875 } 876 if ($this->manageCnAndDescription($modules)) { 877 $this->attributes['cn'][0] = $_POST['cn']; 878 if (preg_match('/^[A-Z]+$/', $_POST['cn'])) { 879 $errors[] = $this->messages['cn'][0]; 880 } 881 // Check if Groupname contains only valid characters 882 if (!get_preg($this->attributes['cn'][0],'groupname')) { 883 $errors[] = $this->messages['cn'][2]; 884 } 885 // Create automatic useraccount with number if original user already exists 886 // Reset name to original name if new name is in use 887 // Set username back to original name if new group name is in use 888 if ($this->groupNameExists($this->attributes['cn'][0]) && ($this->orig['cn'][0] != '')) { 889 $this->attributes['cn'][0] = $this->orig['cn'][0]; 890 } 891 // Change gid to a new gid until a free gid is found 892 else { 893 while ($this->groupNameExists($this->attributes['cn'][0])) { 894 // get last character of group name 895 $lastchar = substr($this->attributes['cn'][0], strlen($this->attributes['cn'][0])-1, 1); 896 // Last character is no number 897 if (!preg_match('/^([0-9])+$/', $lastchar)) { 898 /* Last character is no number. Therefore we only have to 899 * add "2" to it. 900 */ 901 $this->attributes['cn'][0] = $this->attributes['cn'][0] . '2'; 902 } 903 else { 904 /* Last character is a number -> we have to increase the number until we've 905 * found a groupname with trailing number which is not in use. 906 * 907 * $i will show us were we have to split groupname so we get a part 908 * with the groupname and a part with the trailing number 909 */ 910 $i = strlen($this->attributes['cn'][0]) - 1; 911 // Set $i to the last character which is a number in $account_new->general_username 912 while (true) { 913 if (preg_match('/^([0-9])+$/',substr($this->attributes['cn'][0], $i, strlen($this->attributes['cn'][0]) - $i))) { 914 $i--; 915 } 916 else { 917 break; 918 } 919 } 920 // increase last number with one 921 $firstchars = substr($this->attributes['cn'][0], 0, $i+1); 922 $lastchars = substr($this->attributes['cn'][0], $i+1, strlen($this->attributes['cn'][0])-$i); 923 // Put username together 924 $this->attributes['cn'][0] = $firstchars . (intval($lastchars)+1); 925 } 926 } 927 } 928 // Show warning if lam has changed group name 929 if ($this->attributes['cn'][0] != $_POST['cn']) { 930 $errors[] = $this->messages['cn'][1]; 931 } 932 } 933 // show info when gidnumber has changed 934 if (isset($this->orig['gidNumber'][0]) && ($this->orig['gidNumber'][0] != $this->attributes['gidNumber'][0]) 935 && ($this->orig['gidNumber'][0] != '') && !$this->changegids) { 936 $errors[] = $this->messages['gidNumber'][0]; 937 } 938 // Return error-messages 939 return $errors; 940 } 941 942 943 /** 944 * Processes user input of the user selection page. 945 * It checks if all input values are correct and updates the associated LDAP attributes. 946 * 947 * @return array list of info/error messages 948 */ 949 function process_user() { 950 $return = array(); 951 if (!isset($this->attributes['memberUid'])) { 952 $this->attributes['memberUid'] = array(); 953 } 954 if (isset($_POST['members_2']) && isset($_POST['members_left'])) { // Add groups to list 955 // add new group 956 $this->attributes['memberUid'] = @array_merge($this->attributes['memberUid'], $_POST['members_2']); 957 } 958 elseif (isset($_POST['members_1']) && isset($_POST['members_right'])) { // remove groups from list 959 $this->attributes['memberUid'] = array_delete($_POST['members_1'], $this->attributes['memberUid']); 960 } 961 // sync users 962 elseif (isset($_POST['syncGON'])) { 963 $return = array_merge($return, $this->syncGon()); 964 } 965 // sync Windows 966 elseif (isset($_POST['syncWindows'])) { 967 $return = array_merge($return, $this->syncWindows()); 968 } 969 return $return; 970 } 971 972 /** 973 * Syncs with group of names members. 974 * 975 * @param bool $forceDelete force deletion of members 976 * @return array list of status messages 977 */ 978 protected function syncGon($forceDelete = false) { 979 $delete = $forceDelete || (isset($_POST['syncGON_delete']) && ($_POST['syncGON_delete'] == 'on')); 980 $return = array(); 981 $gon = $this->getAccountContainer()->getAccountModule('groupOfNames'); 982 if ($gon == null) { 983 $gon = $this->getAccountContainer()->getAccountModule('groupOfUniqueNames'); 984 } 985 if ($gon == null) { 986 $gon = $this->getAccountContainer()->getAccountModule('groupOfMembers'); 987 } 988 if ($gon == null) { 989 return; 990 } 991 if (!isset($this->attributes['memberUid'])) { 992 $this->attributes['memberUid'] = array(); 993 } 994 $memberDNs = $gon->getMembers(); 995 $users = $this->getUsers(); 996 $oldValues = $this->attributes['memberUid']; 997 if ($delete) { 998 $this->attributes['memberUid'] = array(); 999 } 1000 foreach ($memberDNs as $dn) { 1001 foreach ($users as $userName => $userAttrs) { 1002 if ($userAttrs['dn'] != $dn) { 1003 continue; 1004 } 1005 $this->attributes['memberUid'][] = $userName; 1006 } 1007 } 1008 $added = array_delete($oldValues, $this->attributes['memberUid']); 1009 if (!empty($added)) { 1010 $return[] = array('INFO', _('Added users'), htmlspecialchars(implode(', ', $added))); 1011 } 1012 if ($delete) { 1013 $deleted = array_delete($this->attributes['memberUid'], $oldValues); 1014 if (!empty($deleted)) { 1015 $return[] = array('INFO', _('Removed users'), htmlspecialchars(implode(', ', $deleted))); 1016 } 1017 } 1018 return $return; 1019 } 1020 1021 /** 1022 * Syncs with Windows. 1023 * 1024 * @return array list of status messages 1025 */ 1026 protected function syncWindows() { 1027 $delete = isset($_POST['syncWindows_delete']) && ($_POST['syncWindows_delete'] == 'on'); 1028 $return = array(); 1029 $windows = $this->getAccountContainer()->getAccountModule('windowsGroup'); 1030 if (!isset($this->attributes['memberUid'])) { 1031 $this->attributes['memberUid'] = array(); 1032 } 1033 $windowsAttributes = $windows->getAttributes(); 1034 $memberDNs = array(); 1035 if (!empty($windowsAttributes['member'])) { 1036 $memberDNs = $windowsAttributes['member']; 1037 } 1038 $users = $this->getUsers(); 1039 $oldValues = $this->attributes['memberUid']; 1040 if ($delete) { 1041 $this->attributes['memberUid'] = array(); 1042 } 1043 foreach ($memberDNs as $dn) { 1044 foreach ($users as $userName => $userAttrs) { 1045 if ($userAttrs['dn'] != $dn) { 1046 continue; 1047 } 1048 $this->attributes['memberUid'][] = $userName; 1049 } 1050 } 1051 $added = array_delete($oldValues, $this->attributes['memberUid']); 1052 if (!empty($added)) { 1053 $return[] = array('INFO', _('Added users'), htmlspecialchars(implode(', ', $added))); 1054 } 1055 if ($delete) { 1056 $deleted = array_delete($this->attributes['memberUid'], $oldValues); 1057 if (!empty($deleted)) { 1058 $return[] = array('INFO', _('Removed users'), htmlspecialchars(implode(', ', $deleted))); 1059 } 1060 } 1061 return $return; 1062 } 1063 1064 /** 1065 * Returns a list of modifications which have to be made to the LDAP account. 1066 * 1067 * @return array list of modifications 1068 * <br>This function returns an array with 3 entries: 1069 * <br>array( DN1 ('add' => array($attr), 'remove' => array($attr), 'modify' => array($attr)), DN2 .... ) 1070 * <br>DN is the DN to change. It may be possible to change several DNs (e.g. create a new user and add him to some groups via attribute memberUid) 1071 * <br>"add" are attributes which have to be added to LDAP entry 1072 * <br>"remove" are attributes which have to be removed from LDAP entry 1073 * <br>"modify" are attributes which have to been modified in LDAP entry 1074 * <br>"info" are values with informational value (e.g. to be used later by pre/postModify actions) 1075 */ 1076 function save_attributes() { 1077 // skip saving if account is based on another structural object class 1078 if ($this->is_base_module() && !$this->getAccountContainer()->isNewAccount && !in_array('posixGroup', $this->getAccountContainer()->attributes_orig['objectClass'])) { 1079 return array(); 1080 } 1081 if (!in_array('posixGroup', $this->attributes['objectClass']) && !in_array('posixGroup', $this->orig['objectClass'])) { 1082 // skip saving if the extension was not added/modified 1083 return array(); 1084 } 1085 // auto sync group members 1086 if ($this->isBooleanConfigOptionSet('posixGroup_autoSyncGon')) { 1087 $this->syncGon(true); 1088 } 1089 $return = $this->getAccountContainer()->save_module_attributes($this->attributes, $this->orig); 1090 // Change gids of users and hosts? 1091 if ($this->changegids) { 1092 // find all accounts to change 1093 $result = searchLDAPByFilter('(&(objectClass=posixAccount)(gidNumber=' . $this->orig['gidNumber'][0] . '))', array('dn'), array('user', 'host')); 1094 if (sizeof($result) > 0) { 1095 for ($i = 0; $i < sizeof($result); $i++) { 1096 $return[$result[$i]['dn']]['modify']['gidNumber'][0] = $this->attributes['gidNumber'][0]; 1097 } 1098 } 1099 } 1100 return $return; 1101 } 1102 1103 /** 1104 * Loads the values of an account profile into internal variables. 1105 * 1106 * @param array $profile hash array with profile values (identifier => value) 1107 */ 1108 function load_profile($profile) { 1109 // profile mappings in meta data 1110 parent::load_profile($profile); 1111 // add extension 1112 if (isset($profile['posixGroup_addExt'][0]) && ($profile['posixGroup_addExt'][0] == "true") 1113 && !in_array('posixGroup', $this->attributes['objectClass'])) { 1114 $this->attributes['objectClass'][] = 'posixGroup'; 1115 } 1116 } 1117 1118 /** 1119 * Returns one or more free GID numbers. 1120 * 1121 * @param integer $count Number of needed free GIDs. 1122 * @param array $errors list of error messages where errors can be added 1123 * @param ConfiguredType $type account type 1124 * @return mixed Null if no GIDs are free else an array of free GIDs. 1125 */ 1126 function getNextGIDs($count, &$errors, $type) { 1127 $typeId = $type->getId(); 1128 // check if UIDs should be taken from Samba pool entry 1129 if (isset($this->moduleSettings['posixGroup_' . $typeId . '_gidGenerator']) && ($this->moduleSettings['posixGroup_' . $typeId . '_gidGenerator'][0] == 'sambaPool')) { 1130 return $this->getNextSambaPoolGIDs($count, $errors, $typeId); 1131 } 1132 // check if UIDs should be taken from domain info entry 1133 if (isset($this->moduleSettings['posixGroup_' . $typeId . '_gidGenerator']) && ($this->moduleSettings['posixGroup_' . $typeId . '_gidGenerator'][0] == 'windowsDomain')) { 1134 return $this->getNextDomainInfoGIDs($count, $errors, $typeId); 1135 } 1136 // use magic number 1137 if (isset($this->moduleSettings['posixGroup_' . $typeId . '_gidGenerator']) && ($this->moduleSettings['posixGroup_' . $typeId . '_gidGenerator'][0] == 'magicNumber')) { 1138 $return = array(); 1139 for ($i = 0; $i < $count; $i++) { 1140 $return[] = $this->moduleSettings['posixGroup_' . $typeId . '_magicNumber'][0]; 1141 } 1142 return $return; 1143 } 1144 $ret = array(); 1145 $minID = intval($this->moduleSettings['posixGroup_' . $typeId . '_minGID'][0]); 1146 $maxID = intval($this->moduleSettings['posixGroup_' . $typeId . '_maxGID'][0]); 1147 $gidList = $this->getGIDs($type); 1148 $gids = array(); 1149 foreach ($gidList as $gid) { 1150 if (($gid <= $maxID) && ($gid >= $minID)) { 1151 $gids[] = $gid; // ignore GIDs > maxID and GIDs < minID 1152 } 1153 } 1154 for ($i = 0; $i < $count; $i++) { 1155 if (count($gids) != 0) { 1156 // there already are some GIDs 1157 // store highest id-number 1158 $id = $gids[count($gids)-1]; 1159 // Return minimum allowed id-number if all found id-numbers are too low 1160 if ($id < $minID) { 1161 $ret[] = $minID; 1162 $gids[] = $minID; 1163 } 1164 // return highest used id-number + 1 if it's still in valid range 1165 elseif ($id < $maxID) { 1166 $ret[] = $id + 1; 1167 $gids[] = $id + 1; 1168 } 1169 // find free numbers between existing ones 1170 else { 1171 $k = intval($minID); 1172 while (in_array($k, $gids)) { 1173 $k++; 1174 } 1175 if ($k > $maxID) { 1176 return null; 1177 } 1178 else { 1179 $ret[] = $k; 1180 $gids[] = $k; 1181 sort ($gids, SORT_NUMERIC); 1182 } 1183 // show warning message 1184 $errors[] = $this->messages['gidNumber'][2]; 1185 } 1186 } 1187 else { 1188 // return minimum allowed id-number if no id-numbers are found 1189 $ret[] = $minID; 1190 $gids[] = $minID; 1191 } 1192 } 1193 return $ret; 1194 } 1195 1196 /** 1197 * Gets the free GID numbers from an Samba pool entry in LDAP. 1198 * 1199 * @param integer $count number of needed free GIDs. 1200 * @param array $errors list of error messages where errors can be added 1201 * @param string $typeId account type id 1202 * @return mixed null if no GIDs are free else an array of free GIDs 1203 */ 1204 private function getNextSambaPoolGIDs($count, &$errors, $typeId) { 1205 $dn = $this->moduleSettings['posixGroup_' . $typeId . '_sambaIDPoolDN'][0]; 1206 $attrs = ldapGetDN($dn, array('gidNumber')); 1207 if (isset($attrs['gidnumber'][0]) && ($attrs['gidnumber'][0] != '')) { 1208 $newValue = $attrs['gidnumber'][0] + $count; 1209 $ldapHandle = $_SESSION['ldap']->server(); 1210 ldap_modify($ldapHandle, $dn, array('gidnumber' => array($newValue))); 1211 logNewMessage(LOG_DEBUG, 'Updated Samba ID pool ' . $dn . ' with GID number ' . $newValue . ' and LDAP code ' . ldap_errno($ldapHandle)); 1212 if (ldap_errno($ldapHandle) != 0) { 1213 logNewMessage(LOG_NOTICE, 'Updating Samba ID pool ' . $dn . ' with GID number ' . $newValue . ' failed. ' . ldap_error($ldapHandle)); 1214 return null; 1215 } 1216 $result = array(); 1217 for ($i = 0; $i < $count; $i++) { 1218 $result[] = $attrs['gidnumber'][0] + $i; 1219 } 1220 return $result; 1221 } 1222 return null; 1223 } 1224 1225 /** 1226 * Gets the free GID numbers from an Windows domain info entry in LDAP. 1227 * 1228 * @param integer $count number of needed free GIDs. 1229 * @param array $errors list of error messages where errors can be added 1230 * @param string $typeId account type id 1231 * @return mixed null if no GIDs are free else an array of free GIDs 1232 */ 1233 private function getNextDomainInfoGIDs($count, &$errors, $typeId) { 1234 $dn = $this->moduleSettings['posixGroup_' . $typeId . '_windowsIDPoolDN'][0]; 1235 $attrs = ldapGetDN($dn, array('msSFU30MaxGidNumber')); 1236 if (isset($attrs['mssfu30maxgidnumber'][0]) && ($attrs['mssfu30maxgidnumber'][0] != '')) { 1237 $newValue = $attrs['mssfu30maxgidnumber'][0] + $count; 1238 $ldapHandle = $_SESSION['ldap']->server(); 1239 ldap_modify($ldapHandle, $dn, array('mssfu30maxgidnumber' => array($newValue))); 1240 logNewMessage(LOG_DEBUG, 'Updated domain info ' . $dn . ' with GID number ' . $newValue . ' and LDAP code ' . ldap_errno($ldapHandle)); 1241 if (ldap_errno($ldapHandle) != 0) { 1242 logNewMessage(LOG_NOTICE, 'Updating domain info ' . $dn . ' with GID number ' . $newValue . ' failed. ' . ldap_error($ldapHandle)); 1243 return null; 1244 } 1245 $result = array(); 1246 for ($i = 0; $i < $count; $i++) { 1247 $result[] = $attrs['mssfu30maxgidnumber'][0] + $i; 1248 } 1249 return $result; 1250 } 1251 return null; 1252 } 1253 1254 /** 1255 * This method specifies if a module manages password attributes. 1256 * @see passwordService::managesPasswordAttributes 1257 * 1258 * @return boolean true if this module manages password attributes 1259 */ 1260 public function managesPasswordAttributes() { 1261 return true; 1262 } 1263 1264 /** 1265 * Specifies if this module supports to force that a user must change his password on next login. 1266 * 1267 * @return boolean force password change supported 1268 */ 1269 public function supportsForcePasswordChange() { 1270 return false; 1271 } 1272 1273 /** 1274 * This function is called whenever the password should be changed. Account modules 1275 * must change their password attributes only if the modules list contains their module name. 1276 * 1277 * @param String $password new password 1278 * @param $modules list of modules for which the password should be changed 1279 * @param boolean $forcePasswordChange force the user to change his password at next login 1280 * @return array list of error messages if any as parameter array for StatusMessage 1281 * e.g. return array(array('ERROR', 'Password change failed.')) 1282 * @see passwordService::passwordChangeRequested 1283 */ 1284 public function passwordChangeRequested($password, $modules, $forcePasswordChange) { 1285 if (!in_array(get_class($this), $modules)) { 1286 return array(); 1287 } 1288 $this->attributes[$this->passwordAttrName][0] = pwd_hash($password, true, $this->moduleSettings['posixAccount_pwdHash'][0]); 1289 return array(); 1290 } 1291 1292 /** 1293 * Returns a list of existing GID numbers. 1294 * 1295 * @param ConfiguredType $type account type 1296 * @return array list of GID numbers 1297 */ 1298 private function getGIDs($type) { 1299 if ($this->cachedGIDList != null) { 1300 return $this->cachedGIDList; 1301 } 1302 $this->cachedGIDList = array(); 1303 $attrs = array('gidNumber'); 1304 $filter = '(&(objectClass=posixGroup)(gidNumber=*))'; 1305 $suffix = $type->getSuffix(); 1306 $typeId = $type->getId(); 1307 if (!empty($this->moduleSettings['posixGroup_' . $typeId . '_gidCheckSuffix'][0])) { 1308 $suffix = $this->moduleSettings['posixGroup_' . $typeId . '_gidCheckSuffix'][0]; 1309 } 1310 $result = searchLDAP($suffix, $filter, $attrs); 1311 for ($i = 0; $i < sizeof($result); $i++) { 1312 $this->cachedGIDList[] = $result[$i]['gidnumber'][0]; 1313 } 1314 sort($this->cachedGIDList, SORT_NUMERIC); 1315 return $this->cachedGIDList; 1316 } 1317 1318 /** 1319 * Returns a list of existing users and their GID numbers and cn. 1320 * 1321 * @return array list in format array(uid => array('gid' => 123, 'cn' => 'Some user')) 1322 */ 1323 protected function getUsers() { 1324 if ($this->cachedUserToGIDList != null) { 1325 return $this->cachedUserToGIDList; 1326 } 1327 $this->cachedUserToGIDList = array(); 1328 $typeManager = new TypeManager(); 1329 foreach ($typeManager->getConfiguredTypesForScope('user') as $type) { 1330 $filter = '(&(objectClass=posixAccount)(gidNumber=*))'; 1331 if ($this->isWindows()) { 1332 $filter = '(&(objectClass=user)(gidNumber=*))'; 1333 } 1334 $result = searchLDAPByFilter($filter, array('uid', 'gidNumber', 'cn'), array('user')); 1335 $resultCount = sizeof($result); 1336 for ($i = 0; $i < $resultCount; $i++) { 1337 $this->cachedUserToGIDList[$result[$i]['uid'][0]] = array( 1338 'gid' => $result[$i]['gidnumber'][0], 1339 'cn' => $result[$i]['cn'][0], 1340 'dn' => $result[$i]['dn']); 1341 } 1342 } 1343 logNewMessage(LOG_DEBUG, 'Found ' . $resultCount . ' Unix users.'); 1344 return $this->cachedUserToGIDList; 1345 } 1346 1347 /** 1348 * Checks if the given group name already exists in LDAP. 1349 * 1350 * @param String $groupName group name 1351 * @return boolean true if already exists 1352 */ 1353 private function groupNameExists($groupName) { 1354 return in_array($groupName, $this->getGroupNames()); 1355 } 1356 1357 /** 1358 * Returns a list of all group names in LDAP. 1359 * 1360 * @return array group names 1361 */ 1362 private function getGroupNames() { 1363 if ($this->cachedGroupNameList != null) { 1364 return $this->cachedGroupNameList; 1365 } 1366 $this->cachedGroupNameList = array(); 1367 $attrs = array('cn'); 1368 $filter = '(&(objectClass=posixGroup)(cn=*))'; 1369 $suffix = $this->getAccountContainer()->get_type()->getSuffix(); 1370 $typeId = $this->getAccountContainer()->get_type()->getId(); 1371 if (!empty($this->moduleSettings['posixGroup_' . $typeId . '_gidCheckSuffix'][0])) { 1372 $suffix = $this->moduleSettings['posixGroup_' . $typeId . '_gidCheckSuffix'][0]; 1373 } 1374 $result = searchLDAP($suffix, $filter, $attrs); 1375 for ($i = 0; $i < sizeof($result); $i++) { 1376 $this->cachedGroupNameList[] = $result[$i]['cn'][0]; 1377 } 1378 return $this->cachedGroupNameList; 1379 } 1380 1381 /** 1382 * Returns if the Windows module is active. 1383 * 1384 * @return boolean is Windows 1385 */ 1386 private function isWindows() { 1387 return (in_array('windowsGroup', $this->getAccountContainer()->get_type()->getModules())); 1388 } 1389 1390 /** 1391 * Returns if cn and description attributes should be managed. 1392 * 1393 * @param string[] $modules modules 1394 * @return boolean manage cn+description 1395 */ 1396 protected function manageCnAndDescription($modules) { 1397 return true; 1398 } 1399 1400} 1401 1402?> 1403