1<?php
2use \LAM\TYPES\TypeManager;
3use function LAM\TYPES\getScopeFromTypeId;
4use LAM\TYPES\ConfiguredType;
5/*
6
7	This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
8	Copyright (C) 2003 - 2006  Tilo Lutz
9    Copyright (C) 2005 - 2020  Roland Gruber
10
11	This program is free software; you can redistribute it and/or modify
12	it under the terms of the GNU General Public License as published by
13	the Free Software Foundation; either version 2 of the License, or
14	(at your option) any later version.
15
16	This program is distributed in the hope that it will be useful,
17	but WITHOUT ANY WARRANTY; without even the implied warranty of
18	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19	GNU General Public License for more details.
20
21	You should have received a copy of the GNU General Public License
22	along with this program; if not, write to the Free Software
23	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24*/
25
26/**
27* Manages Unix accounts for users and hosts.
28*
29* @package modules
30*
31* @author Tilo Lutz
32* @author Roland Gruber
33* @author Michael Duergner
34* @author Thomas Manninger
35*/
36
37/**
38* Manages the object class "posixAccount" for users and hosts.
39*
40* @package modules
41*/
42class posixAccount extends baseModule implements passwordService {
43
44	// Variables
45	/** delimiter for lamdaemon commands */
46	private static $SPLIT_DELIMITER = "###x##y##x###";
47
48	/* These two variables keep an array of groups the user is also member of. */
49	/** current group list */
50	private $groups;
51	/** original group list */
52	private $groups_orig;
53
54	/* list of group of names that the user is member of */
55	/** current group of names list */
56	private $gonList = array();
57	/** original group of names list */
58	private $gonList_orig = array();
59	/** lamdaemon servers */
60	private $lamdaemonServers = array();
61	/** cache for group objects */
62	private $groupCache = null;
63	/** cache for group of names objects */
64	private $gonCache = null;
65	/** clear text password */
66	private $clearTextPassword;
67	/** caches the list of known UIDs */
68	private $cachedUIDList = null;
69	/** caches the list of known user names */
70	private $cachedUserNameList = null;
71
72	/** replacements for common umlauts */
73	private $umlautReplacements = array(
74		'ä' => 'ae', 'Ä' => 'Ae', 'ö' => 'oe', 'Ö' => 'Oe', 'ü' => 'ue', 'Ü' => 'Ue',
75		'ß' => 'ss', 'é' => 'e', 'è' => 'e', 'ô' => 'o', 'ç' => 'c'
76	);
77
78	/**
79	 *  This function fills the error message array with messages.
80	**/
81	function load_Messages() {
82		// error messages for input checks
83		$this->messages['minUID'][0] = array('ERROR', _('Users') . ': &nbsp;' . _('Minimum UID number'), _("Minimum UID number is invalid!"));
84		$this->messages['maxUID'][0] = array('ERROR', _('Users') . ': &nbsp;' . _('Maximum UID number'), _("Maximum UID number is invalid!"));
85		$this->messages['minMachine'][0] = array('ERROR', _('Hosts') . ': &nbsp;' . _('Minimum UID number'), _("Minimum UID number is invalid!"));
86		$this->messages['maxMachine'][0] = array('ERROR', _('Hosts') . ': &nbsp;' . _('Maximum UID number'), _("Maximum UID number is invalid!"));
87		$this->messages['cmp_UID'][0] = array('ERROR', _('Users') . ': &nbsp;' . _('Maximum UID number'), _("Maximum UID number must be greater than minimum UID number!"));
88		$this->messages['cmp_Machine'][0] = array('ERROR', _('Hosts') . ': &nbsp;' . _('Maximum UID number'), _("Maximum UID number must be greater than minimum UID number!"));
89		$this->messages['cmp_both'][0] = array('ERROR', _('UID ranges for Unix accounts'), _("The UID ranges for users and hosts overlap! This is a problem because LAM uses the highest UID in use + 1 for new accounts. Please set the minimum UID to equal values or use independent ranges."));
90		$this->messages['homeDirectory'][0] = array('ERROR', _('Home directory'), _('Homedirectory contains invalid characters.'));
91		$this->messages['homeDirectory'][1] = array('INFO', _('Home directory'), _('Replaced $user or $group in homedir.'));
92		$this->messages['homeDirectory'][2] = array('ERROR', _('Account %s:') . ' posixAccount_homedir', _('Homedirectory contains invalid characters.'));
93		$this->messages['homeDirectory'][3] = array('INFO', _('Home directory'), _('Home directory changed. To keep home directory you have to run the following command as root: \'mv %s %s\''));
94		$this->messages['uidNumber'][1] = array('ERROR', _('ID-Number'), _('No free ID-Number!'));
95		$this->messages['uidNumber'][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.'));
96		$this->messages['uidNumber'][3] = array('ERROR', _('ID-Number'), _('ID is already in use'));
97		$this->messages['uidNumber'][4] = array('ERROR', _('Account %s:') . ' posixAccount_uid', _('UID must be a number. It has to be inside the UID range which is defined in your configuration profile.'));
98		$this->messages['uidNumber'][5] = array('INFO', _('UID number'), _('UID number has changed. To keep file ownership you have to run the following command as root: \'find / -uid %s -exec chown %s {} \;\''));
99		$this->messages['userPassword'][0] = array('ERROR', _('Password'), _('Please enter the same password in both password fields.'));
100		$this->messages['userPassword'][1] = array('ERROR', _('Password'), _('Password contains invalid characters. Valid characters are:') . ' a-z, A-Z, 0-9 and #*,.;:_-+!%&/|?{[()]}=@$ §°!');
101		$this->messages['userPassword'][4] = array('ERROR', _('Account %s:') . ' posixAccount_password', _('Password contains invalid characters. Valid characters are:') . ' a-z, A-Z, 0-9 and #*,.;:_-+!%&/|?{[()]}=@$ §°!');
102		$this->messages['uid'][0] = array('INFO', _('UID'), _('UID has changed. Do you want to change home directory?'));
103		$this->messages['uid'][1] = array('WARN', _('User name'), _('You are using capital letters. This can cause problems because Windows is not case-sensitive.'));
104		$this->messages['uid'][2] = array('ERROR', _('User name'), _('User name contains invalid characters. Valid characters are: a-z, A-Z, 0-9 and .-_ !'));
105		$this->messages['uid'][3] = array('WARN', _('Host name'), _('You are using capital letters. This can cause problems because Windows is not case-sensitive.'));
106		$this->messages['uid'][4] = array('ERROR', _('Host name'), _('Host name contains invalid characters. Valid characters are: a-z, A-Z, 0-9 and .-_ !'));
107		$this->messages['uid'][5] = array('WARN', _('User name'), _('User name in use (%s). Selected next free user name.'));
108		$this->messages['uid'][6] = array('WARN', _('Host name'), _('Host name in use (%s). Selected next free host name.'));
109		$this->messages['uid'][7] = array('ERROR', _('Account %s:') . ' posixAccount_userName', _('User name contains invalid characters. Valid characters are: a-z, A-Z, 0-9 and .-_ !'));
110		$this->messages['uid'][8] = array('ERROR', _('Account %s:') . ' posixAccount_hostName', _('Host name contains invalid characters. Valid characters are: a-z, A-Z, 0-9 and .-_ !'));
111		$this->messages['uid'][9] = array('WARN', _('Account %s:') . ' posixAccount_userName', _('User name already exists!') . ' ' . _('You might want to use %s instead of %s.') . ' %s');
112		$this->messages['uid'][10] = array('WARN', _('Account %s:') . ' posixAccount_hostName', _('Host name already exists!') . ' ' . _('You might want to use %s instead of %s.') . ' %s');
113		$this->messages['gidNumber'][0] = array('ERROR', _('Account %s:') . ' posixAccount_group', _('LAM was unable to find a group with this name!'));
114		$this->messages['gidNumber'][1] = array('ERROR', _('Account %s:') . ' posixAccount_group', _('This GID number is invalid! Please provide either a number or a group name.'));
115		$this->messages['gidNumber'][2] = array('INFO', _('GID number'), _('GID number has changed. To keep file ownership you have to run the following command as root: \'find / -gid %s -uid %s -exec chgrp %s {} \;\''));
116		$this->messages['gecos'][0] = array('ERROR', _('Account %s:') . ' posixAccount_gecos', _('This gecos value is invalid!'));
117		$this->messages['shell'][0] = array('ERROR', _('Account %s:') . ' posixAccount_shell', _('This login shell is invalid!'));
118		$this->messages['passwordDisabled'][0] = array('ERROR', _('Account %s:') . ' posixAccount_passwordDisabled', _('This value can only be "true" or "false".'));
119		$this->messages['cn'][0] = array('ERROR', _('Common name'), _('Please enter a valid common name!'));
120		$this->messages['cn'][1] = array('ERROR', _('Account %s:') . ' posixAccount_cn', _('Please enter a valid common name!'));
121		$this->messages['sambaIDPoolDN'][0] = array('ERROR', _('Samba ID pool DN'), _('This is not a valid DN!'));
122		$this->messages['windowsIDPoolDN'][0] = array('ERROR', _('Windows domain info DN'), _('This is not a valid DN!'));
123	}
124
125	/**
126	* Returns true if this module can manage accounts of the current type, otherwise false.
127	*
128	* @return boolean true if module fits
129	*/
130	public function can_manage() {
131		return in_array($this->get_scope(), array('user', 'host'));
132	}
133
134	/**
135	* Returns meta data that is interpreted by parent class
136	*
137	* @return array array with meta data
138	*
139	* @see baseModule::get_metaData()
140	*/
141	function get_metaData() {
142		$return = array();
143		// icon
144		$return['icon'] = 'tux.png';
145		// user specific data
146		if ($this->get_scope() == "user") {
147			// LDAP filter
148			$return["ldap_filter"] = array('or' => "(objectClass=posixAccount)", 'and' => "(!(uid=*$))");
149			// module dependencies
150			$return['dependencies'] = array('depends' => array(), 'conflicts' => array());
151		}
152		elseif ($this->get_scope() == "host") {
153			// LDAP filter
154			$return["ldap_filter"] = array('or' => "(objectClass=posixAccount)");
155			// module dependencies
156			$return['dependencies'] = array('depends' => array(), 'conflicts' => array());
157		}
158		// alias name
159		$return["alias"] = _("Unix");
160		// RDN attributes
161		$return["RDN"] = array("uid" => "high", "cn" => "low");
162		// managed object classes
163		$return['objectClasses'] = array('posixAccount');
164		// LDAP aliases
165		$return['LDAPaliases'] = array('commonName' => 'cn', 'userid' => 'uid');
166		// managed attributes
167		$return['attributes'] = array('uid', 'uidNumber', 'gidNumber',
168			'loginShell', 'gecos', 'INFO.userPasswordClearText');
169		if ($this->get_scope() == "user") {
170			// self service search attributes
171			$return['selfServiceSearchAttributes'] = array('uid');
172			// self service field settings
173			$return['selfServiceFieldSettings'] = array(
174					'password' => _('Password'),
175					'cn' => _('Common name'),
176					'loginShell' => _('Login shell'),
177					'syncWindowsPassword' => _('Sync Unix password with Windows password'),
178					'unixgroups' => _('Groups (read-only)')
179			);
180			// possible self service read-only fields
181			$return['selfServiceReadOnlyFields'] = array('cn', 'loginShell');
182			// self service configuration settings
183			$selfServiceContainer = new htmlResponsiveRow();
184			$selfServiceContainer->add(new htmlResponsiveSelect('posixAccount_pwdHash', getSupportedHashTypes(),
185				array('SSHA'), _("Password hash type"), array('pwdHash', get_class($this))), 12);
186			$selfServiceContainer->add(new htmlResponsiveInputTextarea('posixAccount_shells', implode("\r\n", $this->getShells()), 30, 4, _('Login shells'), array('loginShells', get_class($this))), 12);
187			$selfServiceContainer->add(new htmlResponsiveInputField(_('Group DN'), 'posixAccount_groupDn', '', array('groupDn', get_class($this))), 12);
188			$selfServiceContainer->add(new htmlResponsiveInputCheckbox('posixAccount_useOldPwd', false, _('Password change with old password'), array('useOldPwd', get_class($this))), 12);
189			$return['selfServiceSettings'] = $selfServiceContainer;
190		}
191		// profile checks
192		$return['profile_checks']['posixAccount_homeDirectory'] = array('type' => 'ext_preg', 'regex' => 'homeDirectory',
193		'error_message' => $this->messages['homeDirectory'][0]);
194		// profile mappings
195		$return['profile_mappings'] = array(
196			'posixAccount_loginShell' => 'loginShell'
197		);
198		// upload
199		$return['upload_preDepends'] = array('inetOrgPerson');
200		// user specific upload options
201		if (($this->get_scope() == 'user') && isLoggedIn()) {
202			$lamdaemonServers = $_SESSION['config']->getConfiguredScriptServers();
203			$lamdaemonOptions = array();
204			foreach ($lamdaemonServers as $lamdaemonServer) {
205				$lamdaemonOptions[] = $lamdaemonServer->getServer();
206			}
207			$return['upload_columns'] = array(
208			array(
209				'name' => 'posixAccount_userName',
210				'description' => _('User name'),
211				'help' => 'uid',
212				'example' => _('smiller'),
213				'required' => true,
214				'unique' => true
215			),
216			array(
217				'name' => 'posixAccount_uid',
218				'description' => _('UID number'),
219				'help' => 'uidNumber',
220				'example' => '1234'
221			),
222			array(
223				'name' => 'posixAccount_group',
224				'description' => _('Primary group'),
225				'help' => 'group_upload',
226				'example' => _('users'),
227				'required' => true
228			),
229			array(
230				'name' => 'posixAccount_additionalGroups',
231				'description' => _('Additional groups'),
232				'help' => 'addgroup_upload',
233				'example' => _('group01,group02')
234			),
235			array(
236				'name' => 'posixAccount_homedir',
237				'description' => _('Home directory'),
238				'help' => 'homeDirectory_upload',
239				'example' => _('/home/smiller'),
240				'default' => '/home/{posixAccount_userName}'
241			),
242			array(
243				'name' => 'posixAccount_createHomeDir',
244				'description' => _('Create home directory'),
245				'help' => 'createhomedir',
246				'example' => 'localhost',
247				'values' => implode(', ', $lamdaemonOptions)
248			),
249			array(
250				'name' => 'posixAccount_shell',
251				'description' => _('Login shell'),
252				'help' => 'loginShell',
253				'example' => '/bin/bash',
254				'values' => implode(", ", $this->getShells()),
255				'default' => '/bin/bash'
256			),
257			);
258			if (self::areGroupOfNamesActive()) {
259				$return['upload_columns'][] = array(
260					'name' => 'posixAccount_gon',
261					'description' => _('Groups of names'),
262					'help' => 'addgroup_upload',
263					'example' => _('group01,group02')
264				);
265			}
266		}
267		// host specific upload options
268		elseif ($this->get_scope() == 'host') {
269			$return['upload_columns'] = array(
270			array(
271				'name' => 'posixAccount_hostName',
272				'description' => _('Host name'),
273				'help' => 'uid',
274				'example' => _('pc01$'),
275				'required' => true,
276				'unique' => true
277			),
278			array(
279				'name' => 'posixAccount_uid',
280				'description' => _('UID number'),
281				'help' => 'uidNumber',
282				'example' => '1234'
283			),
284			array(
285				'name' => 'posixAccount_group',
286				'description' => _('Primary group'),
287				'help' => 'group_upload',
288				'example' => _('machines'),
289				'required' => true
290			),
291			);
292		}
293		// available PDF fields
294		if ($this->get_scope() == 'host') {
295			$return['PDF_fields'] = array('uid' => _('Host name'));
296		}
297		else {
298			$return['PDF_fields'] = array('uid' => _('User name'));
299		}
300		$return['PDF_fields'] = array_merge($return['PDF_fields'], array(
301			'uidNumber' => _('UID number'),
302			'gidNumber' => _('GID number'),
303			'primaryGroup' => _('Primary group'),
304			'additionalGroups' => _('Additional groups'),
305			'homeDirectory' => _('Home directory'),
306			'loginShell' => _('Login shell'),
307			'userPassword' => _('Password')
308		));
309		if (self::areGroupOfNamesActive()) {
310			$return['PDF_fields']['gon'] = _('Groups of names');
311		}
312		// help Entries
313		$return['help'] = array(
314			'autoAdd' => array(
315				"Headline" => _("Automatically add this extension"),
316				"Text" => _("This will enable the extension automatically if this profile is loaded.")
317			),
318			'userNameSuggestion' => array(
319				"Headline" => _("User name suggestion"),
320				"Text" => _("LAM will suggest a user name based on e.g. first and last name. Here you can specify the suggestion. %sn% will be replaced by the last name. @givenname@ will be replaced by the first character of first name. Only attributes of tab Personal may be used.")
321							. '<br>' . _('Common examples are "@givenname@%sn%" or "%givenname%.%sn%".')
322			),
323			'hiddenOptions' => array(
324				"Headline" => _("Hidden options"),
325				"Text" => _("The selected options will not be managed inside LAM. You can use this to reduce the number of displayed input fields.")
326			),
327			'primaryGroupAsSecondary' => array(
328				'Headline' => _('Set primary group as memberUid'),
329				'Text' => _('Usually, users are not added to groups as memberUid if they have this group as primary group. If your application ignores primary groups then you can select this option to override this behaviour.')
330			),
331			'minMaxUser' => array(
332				'Headline' => _('UID number'),
333				'Text' => _('These are the minimum and maximum numbers to use for user IDs when creating new user accounts. The range should be different from that of machines. New user accounts will always get the highest number in use plus one.')
334			),
335			'minMaxHost' => array(
336				'Headline' => _('UID number'),
337				'Text' => _('These are the minimum and maximum numbers to use for machine IDs when creating new accounts for hosts. The range should be different from that of users. New host accounts will always get the highest number in use plus one.')
338			),
339			'pwdHash' => array(
340				"Headline" => _("Password hash type"),
341				"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.")
342						. ' ' . _('K5KEY is only needed if you use Kerberos with smbk5pwd.')
343			),
344			'uidNumber' => array(
345				"Headline" => _("UID number"), 'attr' => 'uidNumber',
346				"Text" => _("If empty UID number will be generated automatically.")
347			),
348			'group_upload' => array(
349				"Headline" => _("Primary group"),
350				"Text" => _("The primary group for this account. You can insert a GID number or a group name.")
351			),
352			'addgroup_upload' => array(
353				"Headline" => _("Additional groups"),
354				"Text" => _("Here you can enter a list of additional group memberships. The group names are separated by commas.")
355			),
356			'homeDirectory_upload' => array(
357				"Headline" => _("Home directory"),
358				"Text" => _("Please enter the path to the user's home directory.")
359			),
360			'homeDirectory' => array(
361					"Headline" => _("Home directory"),
362					"Text" => _("Please enter the path to the user's home directory.")
363			),
364			'deletehomedir' => array(
365				"Headline" => _("Home directory"),
366				"Text" => _("Activating this checkbox will remove the user's home directory.")
367			),
368			'createhomedir' => array(
369				"Headline" => _("Home directory"),
370				"Text" => _("This will create the user's home directory on the specified server.")
371			),
372			'deleteSudoers' => array(
373				"Headline" => _("Delete sudo rights"),
374				"Text" => _("Deletes the user from all existing sudo rights.")
375			),
376			'uidCheckSuffix' => array (
377				"Headline" => _("Suffix for UID/user name check"),
378				"Text" => _("LAM checks if the entered user name and UID 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 user names or UIDs.")
379			),
380			'loginShells' => array(
381				"Headline" => _("Login shells"),
382				"Text" => _("This is the list of valid login shells.")
383			),
384			'uidGenerator' => array (
385				"Headline" => _("UID generator"),
386				"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\".")
387					. ' ' . _('Magic number will set a fixed value that must match your server configuration.')
388			),
389			'sambaIDPoolDN' => array (
390				"Headline" => _("Samba ID pool DN"),
391				"Text" => _("Please enter the DN of the LDAP entry with object class \"sambaUnixIdPool\".")
392			),
393			'windowsIDPoolDN' => array (
394				"Headline" => _("Windows domain info DN"),
395				"Text" => _("Please enter the DN of the LDAP entry with object class \"msSFU30DomainInfo\".")
396			),
397			'magicNumber' => array(
398				"Headline" => _("Magic number"),
399				"Text" => _("Please enter the magic number you configured on server side.")
400			),
401			'noObjectClass' => array(
402				"Headline" => _("Do not add object class"),
403				"Text" => _("This will not add the posixAccount object class to the account.")
404				),
405			'excludeFromGroupSync' => array (
406				"Headline" => _('Exclude from group sync'),
407				"Text" => _('Enter one group per line that should be ignored when syncing groups.')
408			),
409			'groupDn' => array (
410				"Headline" => _('Group DN'),
411				"Text" => _('Enter the base DN of your groups here. This is only required if you want to display memberships on the self service page.')
412			),
413			'user' => array(
414				'uid' => array(
415					"Headline" => _("User name"), 'attr' => 'uid',
416					"Text" => _("User name of the user who should be created. Valid characters are: a-z,A-Z,0-9, @.-_. If user name is already used user name will be expanded with a number. The next free number will be used.")
417				),
418				'gecos' => array(
419					"Headline" => _("Gecos"),
420					"Text" => _("User description. If left empty first and last name will be used.")
421				),
422				'gidNumber' => array(
423					"Headline" => _("Primary group"), 'attr' => 'gidNumber',
424					"Text" => _("The primary group the user should be member of.")
425				),
426				'userPassword' => array(
427					"Headline" => _("Password"),
428					"Text" => _("Please enter the password which you want to set for this account.")
429				),
430				'userPassword_lock' => array(
431					"Headline" => _("Account deactivated"),
432					"Text" => _("If checked account will be deactivated by putting a \"!\" before the encrypted password.")
433				),
434				'loginShell' => array(
435					"Headline" => _("Login shell"),
436					"Text" => _("To disable login use /bin/false.")
437				),
438				'addgroup' => array(
439					"Headline" => _("Additional groups"),
440					"Text" => _("Hold the CTRL-key to (de)select multiple groups."). ' '. _("Can be left empty.")
441				),
442				'cn' => array (
443					"Headline" => _("Common name"), 'attr' => 'cn',
444					"Text" => _("This is the natural name of the user. If empty, the first and last name or user name is used.")
445				),
446				'useOldPwd' => array (
447					"Headline" => _('Password change with old password'),
448					"Text" => _('Sends the old password together with the new password when the user sets a new password.')
449				)
450			),
451			'host' => array(
452				'uid' => array(
453					"Headline" => _("Host name"), 'attr' => 'uid',
454					"Text" => _("Host name of the host which should be created. Valid characters are: a-z,A-Z,0-9, .-_$. Host names are always ending with $. If last character is not $ it will be added. If host name is already used host name will be expanded with a number. The next free number will be used.")
455				),
456				'gecos' => array(
457					"Headline" => _("Gecos"),
458					"Text" => _("Host description. If left empty host name will be used.")
459				),
460				'gidNumber' => array(
461					"Headline" => _("Primary group"), 'attr' => 'gidNumber',
462					"Text" => _("The primary group the host should be member of.")
463				),
464				'description' => array (
465					"Headline" => _("Description"),
466					"Text" => _("Host description. If left empty host name will be used.")
467				),
468				'cn' => array (
469					"Headline" => _("Common name"), 'attr' => 'cn',
470					"Text" => _("This is the natural name of the host. If empty, the host name will be used.")
471				)
472			)
473		);
474
475		return $return;
476	}
477
478	/**
479	* Initializes the module after it became part of an accountContainer
480	*
481	* @param string $base the name of the accountContainer object ($_SESSION[$base])
482	*/
483	function init($base) {
484		// make optional if needed
485		$modules = $_SESSION[$base]->get_type()->getModules();
486		$this->autoAddObjectClasses = !$this->isOptional($modules) && !$this->skipObjectClass();
487		// call parent init
488		parent::init($base);
489		$this->groups = array();
490		$this->groups_orig = array();
491		// list of all group names
492		$groups = $this->findGroups($modules);
493		if (count($groups)==0) {
494			StatusMessage("ERROR", _('No Unix groups found in LDAP! Please create one first.'), '');
495			return;
496		}
497		$this->gonList = array();
498		$this->gonList_orig = array();
499	}
500
501	/**
502	 * {@inheritDoc}
503	 * @see baseModule::getManagedAttributes()
504	 */
505	public function getManagedAttributes($typeId) {
506		$attrs = parent::getManagedAttributes($typeId);
507		$typeManager = new TypeManager();
508		if (!$typeManager->hasConfig()) {
509			return $attrs;
510		}
511		$modules = $typeManager->getConfiguredType($typeId)->getModules();
512		if ($this->manageCn($modules)) {
513			$attrs[] = 'cn';
514		}
515		$attrs[] = $this->getHomedirAttrName($modules);
516		$attrs[] = $this->getPasswordAttrName($modules);
517		return $attrs;
518	}
519
520	/**
521	* This functions is used to check if all settings for this module have been made.
522	*
523	* @return boolean true, if settings are complete
524	*/
525	function module_complete() {
526		if (!$this->skipObjectClass() && (!isset($this->attributes['objectClass']) || !in_array('posixAccount', $this->attributes['objectClass']))) {
527			// no checks if object class is not set
528			return true;
529		}
530		if (!isset($this->attributes['uid'][0]) || ($this->attributes['uid'][0] == '')) {
531			return false;
532		}
533		if (!isset($this->attributes['uidNumber'][0]) || ($this->attributes['uidNumber'][0] == '')) {
534			return false;
535		}
536		if (!isset($this->attributes['gidNumber'][0]) || ($this->attributes['gidNumber'][0] == '')) {
537			return false;
538		}
539		if (!isset($this->attributes['loginShell'][0]) || ($this->attributes['loginShell'][0] == '')) {
540			return false;
541		}
542		return true;
543	}
544
545	/**
546	 * This function loads all needed LDAP attributes.
547	 *
548	 * @param array $attr list of attributes
549	 */
550	function load_attributes($attr) {
551		parent::load_attributes($attr);
552		$typeSettings = $_SESSION['config']->get_typeSettings();
553		// get additional group memberships
554		if (!isset($attr['uid'][0])) {
555			return;
556		}
557		$groupFilter = '(&(objectClass=posixGroup)(memberUid=' . $attr['uid'][0] . '))';
558		if (!empty($typeSettings['filter_group'])) {
559			$typeFilter = $typeSettings['filter_group'];
560			if (strpos($typeFilter, '(') !== 0) {
561				$typeFilter = '(' . $typeFilter . ')';
562			}
563			$groupFilter = '(&' . $groupFilter . $typeFilter . ')';
564		}
565		$groupList = searchLDAPByFilter($groupFilter, array('cn'), array('group'));
566		for ($i = 0; $i < sizeof($groupList); $i++) {
567			$this->groups[] = $groupList[$i]['cn'][0];
568		}
569		$this->groups_orig = $this->groups;
570		// get additional group of names memberships
571		if (self::areGroupOfNamesActive()) {
572			$types = array('gon', 'group');
573			$gonList = array();
574			foreach ($types as $type) {
575				$gonFilter = '(|(&(objectClass=groupOfNames)(member=' . ldap_escape($this->getAccountContainer()->dn_orig) . '))(&(objectClass=groupOfMembers)(member=' . $this->getAccountContainer()->dn_orig . '))(&(objectClass=groupOfUniqueNames)(uniqueMember=' . $this->getAccountContainer()->dn_orig . ')))';
576				if (!empty($typeSettings['filter_' . $type])) {
577					$typeFilter = $typeSettings['filter_' . $type];
578					if (strpos($typeFilter, '(') !== 0) {
579						$typeFilter = '(' . $typeFilter . ')';
580					}
581					$gonFilter = '(&' . $gonFilter . $typeFilter . ')';
582				}
583				$gonListPart = searchLDAPByFilter($gonFilter, array('dn'), array($type));
584				$gonList = array_merge($gonList, $gonListPart);
585			}
586			$this->gonList_orig = array();
587			for ($i = 0; $i < sizeof($gonList); $i++) {
588				$this->gonList_orig[] = $gonList[$i]['dn'];
589			}
590			$this->gonList_orig = array_values(array_unique($this->gonList_orig));
591			$this->gonList = $this->gonList_orig;
592		}
593	}
594
595	/**
596	* Returns a list of modifications which have to be made to the LDAP account.
597	*
598	* @return array list of modifications
599	* <br>This function returns an array with 3 entries:
600	* <br>array( DN1 ('add' => array($attr), 'remove' => array($attr), 'modify' => array($attr)), DN2 .... )
601	* <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)
602	* <br>"add" are attributes which have to be added to LDAP entry
603	* <br>"remove" are attributes which have to be removed from LDAP entry
604	* <br>"modify" are attributes which have to been modified in LDAP entry
605	* <br>"info" are values with informational value (e.g. to be used later by pre/postModify actions)
606	*/
607	function save_attributes() {
608		if (!$this->skipObjectClass() && (!in_array('posixAccount', $this->attributes['objectClass']) && !in_array('posixAccount', $this->orig['objectClass']))) {
609			// skip saving if the extension was not added/modified
610			return array();
611		}
612		$modules = $this->getAccountContainer()->get_type()->getModules();
613		// get default changes
614		$return = $this->getAccountContainer()->save_module_attributes($this->attributes, $this->orig);
615		// add information about clear text password and password status change
616		$return[$this->getAccountContainer()->dn_orig]['info']['userPasswordClearText'][0] = $this->clearTextPassword;
617		$pwdAttrName = $this->getPasswordAttrName($modules);
618		if (isset($this->orig[$pwdAttrName][0]) && isset($this->attributes[$pwdAttrName][0])) {
619			if ((pwd_is_enabled($this->orig[$pwdAttrName][0]) && pwd_is_enabled($this->attributes[$pwdAttrName][0]))
620				|| (!pwd_is_enabled($this->orig[$pwdAttrName][0]) && !pwd_is_enabled($this->attributes[$pwdAttrName][0]))) {
621				$return[$this->getAccountContainer()->dn_orig]['info']['userPasswordStatusChange'][0] = 'unchanged';
622			}
623			elseif (pwd_is_enabled($this->orig[$pwdAttrName][0])) {
624				$return[$this->getAccountContainer()->dn_orig]['info']['userPasswordStatusChange'][0] = 'locked';
625			}
626			else {
627				$return[$this->getAccountContainer()->dn_orig]['info']['userPasswordStatusChange'][0] = 'unlocked';
628			}
629		}
630		if ($this->skipObjectClass() || in_array('posixAccount', $this->attributes['objectClass'])) {
631			// Remove primary group from additional groups
632			if (!isset($this->moduleSettings['posixAccount_primaryGroupAsSecondary'][0])
633				|| ($this->moduleSettings['posixAccount_primaryGroupAsSecondary'][0] != 'true')) {
634				for ($i = 0; $i < count($this->groups); $i++) {
635					if ($this->groups[$i] == $this->getGroupName($this->attributes['gidNumber'][0])) {
636						unset($this->groups[$i]);
637					}
638				}
639			}
640			else {
641				// add user as memberuid in primary group
642				$primaryGroupName = $this->getGroupName($this->attributes['gidNumber'][0]);
643				if (!in_array($primaryGroupName, $this->groups)) {
644					$this->groups[] = $primaryGroupName;
645				}
646				// add user as member in group of names if auto-sync is activated
647				if ($this->isBooleanConfigOptionSet('posixGroup_autoSyncGon')) {
648					$allGons = $this->findGroupOfNames();
649					foreach ($allGons as $gonDn => $gonData) {
650						if (in_array_ignore_case('posixGroup', $gonData['objectclass'])) {
651							$gonCn =  $gonData['cn'][0];
652							if (($gonCn === $primaryGroupName) && !in_array($gonDn, $this->gonList)) {
653								$this->gonList[] = $gonDn;
654							}
655						}
656					}
657				}
658			}
659
660			// Set additional group memberships
661			if (isset($this->orig['uid'][0]) && ($this->orig['uid'][0] != '') && ($this->attributes['uid'][0] != $this->orig['uid'][0])) {
662				// find affected groups
663				$groupList = searchLDAPByAttribute('memberUid', $this->orig['uid'][0], 'posixGroup', array('dn'), array('group'));
664				for ($i = 0; $i < sizeof($groupList); $i++) {
665					// replace old user name with new one
666					$return[$groupList[$i]['dn']]['remove']['memberUid'][] = $this->orig['uid'][0];
667					$return[$groupList[$i]['dn']]['add']['memberUid'][] = $this->attributes['uid'][0];
668				}
669			}
670			else {
671				// update groups.
672				$add = array_delete($this->groups_orig, $this->groups);
673				$remove = array_delete($this->groups, $this->groups_orig);
674				$groupList = searchLDAPByAttribute('cn', '*', 'posixGroup', array('cn', 'dn'), array('group'));
675				$cn2dn = array();
676				for ($i = 0; $i < sizeof($groupList); $i++) {
677					$cn2dn[$groupList[$i]['cn'][0]] = $groupList[$i]['dn'];
678				}
679				for ($i = 0; $i < sizeof($add); $i++) {
680					if (isset($cn2dn[$add[$i]])) {
681						$return[$cn2dn[$add[$i]]]['add']['memberUid'][] = $this->attributes['uid'][0];
682					}
683				}
684				for ($i = 0; $i < sizeof($remove); $i++) {
685					if (isset($cn2dn[$remove[$i]])) {
686						$return[$cn2dn[$remove[$i]]]['remove']['memberUid'][] = $this->attributes['uid'][0];
687					}
688				}
689			}
690		}
691		elseif (in_array('posixAccount', $this->orig['objectClass']) && !empty($this->orig['uid'][0])) {
692			// Unix extension was removed, clean group memberships
693			$groupList = searchLDAPByAttribute('memberUid', $this->orig['uid'][0], 'posixGroup', array('dn'), array('group'));
694			for ($i = 0; $i < sizeof($groupList); $i++) {
695				// remove user name
696				$return[$groupList[$i]['dn']]['remove']['memberUid'][] = $this->orig['uid'][0];
697			}
698		}
699		return $return;
700	}
701
702	/**
703	 * Runs the postmodify actions.
704	 *
705	 * @see baseModule::postModifyActions()
706	 *
707	 * @param boolean $newAccount
708	 * @param array $attributes LDAP attributes of this entry
709	 * @return array array which contains status messages. Each entry is an array containing the status message parameters.
710	 */
711	public function postModifyActions($newAccount, $attributes) {
712		$messages = array();
713		$accountContainer = $this->getAccountContainer();
714		if ($accountContainer == null) {
715			return $messages;
716		}
717		$modules = $accountContainer->get_type()->getModules();
718		// set exop password
719		$messages = array_merge($messages, $this->setExopPassword($this->moduleSettings));
720		// create home directories if needed
721		$homeDirAttr = $this->getHomedirAttrName($modules);
722		$lamdaemonServerList = $_SESSION['config']->getConfiguredScriptServers();
723		if (sizeof($this->lamdaemonServers) > 0) {
724			foreach ($lamdaemonServerList as $lamdaemonServer) {
725				if (!in_array($lamdaemonServer->getServer(), $this->lamdaemonServers)) {
726					continue;
727				}
728				$remote = new \LAM\REMOTE\Remote();
729				$remote->connect($lamdaemonServer);
730				$result = $remote->execute(
731					implode(
732						self::$SPLIT_DELIMITER,
733						array(
734							$this->attributes['uid'][0],
735							"home",
736							"add",
737							$lamdaemonServer->getHomeDirPrefix() . $this->attributes[$homeDirAttr][0],
738							"0".$_SESSION['config']->get_scriptRights(),
739							$this->attributes['uidNumber'][0],
740							$this->attributes['gidNumber'][0])
741						));
742				$remote->disconnect();
743				// lamdaemon results
744				if (!empty($result)) {
745					$singleresult = explode(",", $result);
746					if (($singleresult[0] == 'ERROR') || ($singleresult[0] == 'INFO') || ($singleresult[0] == 'WARN')) {
747						$messages[] = $singleresult;
748					}
749					else {
750						$messages[] = array('ERROR', $result[0]);
751					}
752				}
753			}
754		}
755		// move home directory if needed
756		if (!empty($this->orig[$homeDirAttr][0]) && !empty($this->attributes[$homeDirAttr][0])
757			&& ($this->orig[$homeDirAttr][0] != $this->attributes[$homeDirAttr][0])) {
758			foreach ($lamdaemonServerList as $lamdaemonServer) {
759				$remote = new \LAM\REMOTE\Remote();
760				$remote->connect($lamdaemonServer);
761				$result = $remote->execute(
762					implode(
763						self::$SPLIT_DELIMITER,
764						array(
765							$this->attributes['uid'][0],
766							"home",
767							"move",
768							$lamdaemonServer->getHomeDirPrefix() . $this->orig[$homeDirAttr][0],
769							$this->attributes['uidNumber'][0],
770							$lamdaemonServer->getHomeDirPrefix() . $this->attributes[$homeDirAttr][0])
771						));
772				$remote->disconnect();
773				// lamdaemon results
774				if (!empty($result)) {
775					$singleresult = explode(",", $result);
776					if (($singleresult[0] == 'ERROR') || ($singleresult[0] == 'INFO') || ($singleresult[0] == 'WARN')) {
777						$messages[] = $singleresult;
778					}
779				}
780			}
781		}
782		// set new group on homedirectory
783		if (!empty($this->orig[$homeDirAttr][0]) && !empty($this->attributes[$homeDirAttr][0])
784			&& ($this->orig['gidNumber'][0] != $this->attributes['gidNumber'][0])) {
785			foreach ($lamdaemonServerList as $lamdaemonServer) {
786				$remote = new \LAM\REMOTE\Remote();
787				$remote->connect($lamdaemonServer);
788				$result = $remote->execute(
789					implode(
790						self::$SPLIT_DELIMITER,
791						array(
792							$this->attributes['uid'][0],
793							"home",
794							"chgrp",
795							$lamdaemonServer->getHomeDirPrefix() . $this->attributes[$homeDirAttr][0],
796							$this->attributes['uidNumber'][0],
797							$this->attributes['gidNumber'][0])
798						));
799				$remote->disconnect();
800				// lamdaemon results
801				if (!empty($result)) {
802					$singleresult = explode(",", $result);
803					if (($singleresult[0] == 'ERROR') || ($singleresult[0] == 'INFO') || ($singleresult[0] == 'WARN')) {
804						$messages[] = $singleresult;
805					}
806				}
807			}
808		}
809		// set group of names
810		if (self::areGroupOfNamesActive()) {
811			$gons = $this->findGroupOfNames();
812			$toAdd = array_values(array_diff($this->gonList, $this->gonList_orig));
813			$toRem = array_values(array_diff($this->gonList_orig, $this->gonList));
814			// update groups if DN changed
815			if (isset($accountContainer->dn_orig) && (strtolower($accountContainer->dn_orig) != strtolower($accountContainer->finalDN))) {
816				// update owner/member/uniqueMember attributes
817				$searchAttrs = array('member', 'uniquemember', 'owner');
818				foreach ($searchAttrs as $searchAttr) {
819					$ownerGroups = searchLDAPByAttribute($searchAttr, $accountContainer->dn_orig, null, array('dn', $searchAttr), array('gon', 'group'));
820					for ($i = 0; $i < sizeof($ownerGroups); $i++) {
821						$found = false;
822						$newOwners = $ownerGroups[$i][$searchAttr];
823						for ($o = 0; $o < sizeof($newOwners); $o++) {
824							if ($newOwners[$o] == $accountContainer->dn_orig) {
825								$newOwners[$o] = $accountContainer->finalDN;
826								$found = true;
827								break;
828							}
829						}
830						if ($found) {
831							$attributesToModify = array($searchAttr => $newOwners);
832							$success = @ldap_mod_replace($_SESSION['ldap']->server(), $ownerGroups[$i]['dn'], $attributesToModify);
833							if (!$success) {
834								$ldapError = getDefaultLDAPErrorString($_SESSION['ldap']->server());
835								logNewMessage(LOG_ERR, 'Unable to modify attributes of DN: ' . $ownerGroups[$i]['dn'] . ' (' . $ldapError . ').');
836								logNewMessage(LOG_DEBUG, print_r($attributesToModify, true));
837								$messages[] = array('ERROR', sprintf(_('Was unable to modify attributes of DN: %s.'), $ownerGroups[$i]['dn']), $ldapError);
838							}
839						}
840					}
841				}
842			}
843			// add groups
844			for ($i = 0; $i < sizeof($toAdd); $i++) {
845				if (isset($gons[$toAdd[$i]])) {
846					$attrName = 'member';
847					if (in_array_ignore_case('groupOfUniqueNames', $gons[$toAdd[$i]]['objectclass'])) {
848						$attrName = 'uniqueMember';
849					}
850					$success = @ldap_mod_add($_SESSION['ldap']->server(), $toAdd[$i], array($attrName => array($accountContainer->finalDN)));
851					if (!$success) {
852						logNewMessage(LOG_ERR, 'Unable to add user ' . $accountContainer->finalDN . ' to group: ' . $toAdd[$i] . ' (' . ldap_error($_SESSION['ldap']->server()) . ').');
853						$messages[] = array('ERROR', sprintf(_('Was unable to add attributes to DN: %s.'), $toAdd[$i]), getDefaultLDAPErrorString($_SESSION['ldap']->server()));
854					}
855					else {
856						logNewMessage(LOG_NOTICE, 'Added user ' . $accountContainer->finalDN . ' to group: ' . $toAdd[$i]);
857					}
858				}
859			}
860			// remove groups
861			for ($i = 0; $i < sizeof($toRem); $i++) {
862				if (isset($gons[$toRem[$i]])) {
863					$attrName = 'member';
864					if (in_array_ignore_case('groupOfUniqueNames', $gons[$toRem[$i]]['objectclass'])) {
865						$attrName = 'uniqueMember';
866					}
867					$success = @ldap_mod_del($_SESSION['ldap']->server(), $toRem[$i], array($attrName => array($accountContainer->dn_orig)));
868					if (!$success) {
869						logNewMessage(LOG_ERR, 'Unable to delete user ' . $accountContainer->finalDN . ' from group: ' . $toRem[$i] . ' (' . ldap_error($_SESSION['ldap']->server()) . ').');
870						$messages[] = array('ERROR', sprintf(_('Was unable to remove attributes from DN: %s.'), $toRem[$i]), getDefaultLDAPErrorString($_SESSION['ldap']->server()));
871					}
872					else {
873						logNewMessage(LOG_NOTICE, 'Removed user ' . $accountContainer->finalDN . ' from group: ' . $toRem[$i]);
874					}
875				}
876			}
877		}
878		return $messages;
879	}
880
881	/**
882	 * Sets the password via ldap_exop if configured.
883	 *
884	 * @param array $settings settings
885	 * @return array error message parameters if any
886	 */
887	private function setExopPassword($settings) {
888		if (!empty($this->clearTextPassword) && !empty($settings['posixAccount_pwdHash'][0])
889				&& ($settings['posixAccount_pwdHash'][0] === 'LDAP_EXOP')) {
890			$success = ldap_exop_passwd($_SESSION['ldap']->server(), $this->getAccountContainer()->finalDN, null, $this->clearTextPassword);
891			if (!$success) {
892				return array(array('ERROR', _('Unable to set password'), getExtendedLDAPErrorMessage($_SESSION['ldap']->server())));
893			}
894		}
895		return array();
896	}
897
898	/**
899	* Additional LDAP operations on delete.
900	*
901	* @return List of LDAP operations, same as for save_attributes()
902	*/
903	function delete_attributes() {
904		$return = array();
905		// remove memberUids if set
906		$groups = searchLDAPByAttribute('memberUid', $this->attributes['uid'][0], 'posixGroup', array('dn'), array('group'));
907		for ($i = 0; $i < sizeof($groups); $i++) {
908			$return[$groups[$i]['dn']]['remove']['memberUid'][] = $this->attributes['uid'][0];
909		}
910		// stop here if referential integrity overlay is active
911		$config = $this->getAccountContainer()->get_type()->getTypeManager()->getConfig();
912		if ($config->isReferentialIntegrityOverlayActive()) {
913			return $return;
914		}
915		// remove from group of names
916		$dn = $this->getAccountContainer()->dn_orig;
917		$gons = searchLDAPByFilter('(|(member=' . $dn . ')(uniqueMember=' . $dn . '))', array('member', 'uniqueMember'), array('group', 'gon'));
918		for ($i = 0; $i < sizeof($gons); $i++) {
919			if (isset($gons[$i]['member'])) {
920				$return[$gons[$i]['dn']]['remove']['member'][] = $dn;
921			}
922			elseif (isset($gons[$i]['uniquemember'])) {
923				$return[$gons[$i]['dn']]['remove']['uniqueMember'][] = $dn;
924			}
925		}
926		return $return;
927	}
928
929	/**
930	 * Allows the module to run commands before the LDAP entry is deleted.
931	 *
932	 * @return array Array which contains status messages. Each entry is an array containing the status message parameters.
933	 */
934	function preDeleteActions() {
935		$return = array();
936		// delete home directory
937		if (isset($_POST['deletehomedir']) && ($_POST['deletehomedir'] == 'on')) {
938			$modules = $this->getAccountContainer()->get_type()->getModules();
939			$homeDirAttr = $this->getHomedirAttrName($modules);
940			// get list of lamdaemon servers
941			$lamdaemonServers = $_SESSION['config']->getConfiguredScriptServers();
942			// try to delete directory on all servers
943			foreach ($lamdaemonServers as $lamdaemonServer) {
944				$remote = new \LAM\REMOTE\Remote();
945				try {
946					$remote->connect($lamdaemonServer);
947					$result = $remote->execute(
948						implode(
949							self::$SPLIT_DELIMITER,
950							array(
951								$this->attributes['uid'][0],
952								"home",
953								"rem",
954								$lamdaemonServer->getHomeDirPrefix() . $this->attributes[$homeDirAttr][0],
955								$this->attributes['uidNumber'][0]
956							)
957						));
958					$remote->disconnect();
959				}
960				catch (LAMException $e) {
961					$return[] = array('ERROR', $e->getTitle(), $e->getMessage());
962				}
963				// lamdaemon results
964				if (!empty($result)) {
965					$singleresult = explode(",", $result);
966					if (is_array($singleresult)) {
967						if (($singleresult[0] == 'ERROR') || ($singleresult[0] == 'WARN') || ($singleresult[0] == 'INFO')) {
968							$return[] = $singleresult;
969						}
970					}
971				}
972			}
973		}
974		// delete sudo rights
975		if (isset($_POST['deleteSudoers']) && ($_POST['deleteSudoers'] == 'on')) {
976			$result = searchLDAPByAttribute('sudoUser', $this->attributes['uid'][0], 'sudoRole', array('dn'), array('sudo'));
977			foreach ($result as $attrs) {
978				$dn = $attrs['dn'];
979				$success = @ldap_mod_del($_SESSION['ldap']->server(), $dn, array('sudoUser' => array($this->attributes['uid'][0])));
980				if (!$success) {
981					$return[] = array('ERROR', getDefaultLDAPErrorString($_SESSION['ldap']->server()));
982				}
983			}
984		}
985		return $return;
986	}
987
988	/**
989	* Processes user input of the primary module page.
990	* It checks if all input values are correct and updates the associated LDAP attributes.
991	*
992	* @return array list of info/error messages
993	*/
994	function process_attributes() {
995		$keysToReplace = array('cn', 'gecos', 'homeDirectory');
996		$this->getAccountContainer()->replaceWildcardsInPOST($keysToReplace);
997		$modules = $this->getAccountContainer()->get_type()->getModules();
998		$typeId = $this->getAccountContainer()->get_type()->getId();
999		$errors = array();
1000		if (isset($_POST['addObjectClass'])) {
1001			if (!isset($this->attributes['objectClass'])) {
1002				$this->attributes['objectClass'] = array();
1003			}
1004			if (!in_array('posixAccount', $this->attributes['objectClass'])) {
1005				$this->attributes['objectClass'][] = 'posixAccount';
1006			}
1007			return $errors;
1008		}
1009		if (isset($_POST['remObjectClass'])) {
1010			$this->attributes['objectClass'] = array_delete(array('posixAccount'), $this->attributes['objectClass']);
1011			$attrs = $this->getManagedAttributes($this->getAccountContainer()->get_type()->getId());
1012			foreach ($attrs as $name) {
1013				if (isset($this->attributes[$name])) {
1014					unset($this->attributes[$name]);
1015				}
1016			}
1017			return $errors;
1018		}
1019		// skip processing if object class is not set
1020		if ($this->isOptional($modules) && !$this->skipObjectClass() && (!isset($this->attributes['objectClass']) || !in_array('posixAccount', $this->attributes['objectClass']))) {
1021			return $errors;
1022		}
1023		$groups = $this->findGroups($modules); // list of all group names
1024		if (count($groups)==0) {
1025			// abort if no groups were found
1026			return array();
1027		}
1028		if (isset($_POST['loginShell'])) {
1029			$this->attributes['loginShell'][0] = $_POST['loginShell'];
1030		}
1031		if (!$this->isBooleanConfigOptionSet('posixAccount_' . $typeId . '_hidegecos')) {
1032			if (isset($_POST['gecos'])) {
1033				$this->attributes['gecos'][0] = $_POST['gecos'];
1034			}
1035		}
1036		if (isset($this->orig['uid'][0]) && ($this->orig['uid'][0] != '') && (trim($_POST['uid']) != $this->attributes['uid'][0])) {
1037			$errors[] = $this->messages['uid'][0];
1038		}
1039		if (isset($this->orig['gidNumber'][0]) && ($this->orig['gidNumber'][0] != '') && ($_POST['gidNumber'] != $this->attributes['gidNumber'][0])) {
1040			$errorMessage = $this->messages['gidNumber'][2];
1041			$errorMessage[] = array($this->orig['gidNumber'][0], $this->orig['uidNumber'][0], $_POST['gidNumber']);
1042			$errors[] = $errorMessage;
1043			if ($this->isBooleanConfigOptionSet('posixAccount_primaryGroupAsSecondary') && !empty($this->attributes['gidNumber'][0])) {
1044				// change primary group in $this->groups
1045				$oldGroupName = $this->getGroupName($this->attributes['gidNumber'][0]);
1046				$newGroupName = $this->getGroupName($_POST['gidNumber']);
1047				if (!empty($oldGroupName) && !empty($newGroupName)) {
1048					$this->groups = array_delete(array($oldGroupName), $this->groups);
1049					$this->groups[] = $newGroupName;
1050					// sync group of names if needed
1051					if ($this->isBooleanConfigOptionSet('posixGroup_autoSyncGon')) {
1052						$allGons = $this->findGroupOfNames();
1053						foreach ($allGons as $gonDn => $gonData) {
1054							if (in_array_ignore_case('posixGroup', $gonData['objectclass'])) {
1055								$gonCn =  $gonData['cn'][0];
1056								if (($gonCn === $newGroupName) && !in_array($gonDn, $this->gonList)) {
1057									$this->gonList[] = $gonDn;
1058								}
1059								if (($gonCn === $oldGroupName) && in_array($gonDn, $this->gonList)) {
1060									$this->gonList = array_delete(array($gonDn), $this->gonList);
1061								}
1062							}
1063						}
1064					}
1065				}
1066			}
1067		}
1068		if (isset($this->orig['uidNumber'][0]) && $this->orig['uidNumber'][0]!='' && trim($_POST['uidNumber'])!=$this->attributes['uidNumber'][0]) {
1069			$errorMessage = $this->messages['uidNumber'][5];
1070			$errorMessage[] = array($this->orig['uidNumber'][0], $_POST['uidNumber']);
1071			$errors[] = $errorMessage;
1072		}
1073		$homedirAttrName = $this->getHomedirAttrName($modules);
1074		if (isset($_POST['homeDirectory']) && isset($this->orig[$homedirAttrName][0]) && ($this->orig[$homedirAttrName][0] != '') && ($_POST['homeDirectory'] != $this->attributes[$homedirAttrName][0])) {
1075			$errorMessage = $this->messages['homeDirectory'][3];
1076			$errorMessage[] = array($this->orig[$homedirAttrName][0], $_POST['homeDirectory']);
1077			$errors[] = $errorMessage;
1078		}
1079		// get list of DNS names or IPs
1080		$lamdaemonServers = $_SESSION['config']->getConfiguredScriptServers();
1081		$this->lamdaemonServers = array();
1082		for ($h = 0; $h < sizeof($lamdaemonServers); $h++) {
1083			if (isset($_POST['createhomedir_' . $h]) && ($_POST['createhomedir_' . $h] = 'on')) {
1084				$this->lamdaemonServers[] = $lamdaemonServers[$h]->getServer();
1085			}
1086		}
1087		if (isset($_POST['homeDirectory'])) {
1088			$this->attributes[$homedirAttrName][0] = $_POST['homeDirectory'];
1089		}
1090		// Load attributes
1091		if ($this->isPasswordManaged()) {
1092			if (isset($_POST['lockPassword'])) {
1093				$this->lock($modules);
1094			}
1095			if (isset($_POST['unlockPassword'])) {
1096				$this->unlock($modules);
1097			}
1098			if (isset($_POST['removePassword'])) {
1099				unset($this->attributes[$this->getPasswordAttrName($modules)]);
1100			}
1101		}
1102		if ($this->manageCn($modules)) {
1103			$this->processMultiValueInputTextField('cn', $errors, 'cn');
1104		}
1105		$this->attributes['uidNumber'][0] = trim($_POST['uidNumber']);
1106		$this->attributes['gidNumber'][0] = $_POST['gidNumber'];
1107		if ($this->get_scope() == 'user') {
1108			if (($this->attributes['uid'][0] != $_POST['uid']) && !get_preg($_POST['uid'], '!upper')) {
1109				$errors[] = $this->messages['uid'][1];
1110			}
1111			if ( !get_preg($this->attributes[$homedirAttrName][0], 'homeDirectory' )) {
1112				$errors[] = $this->messages['homeDirectory'][0];
1113			}
1114		}
1115		$this->attributes['uid'][0] = trim($_POST['uid']);
1116		// Check if UID is valid. If none value was entered, the next usable value will be inserted
1117		// load min and may uidNumber
1118		if ($this->get_scope()=='user') {
1119			$minID = intval($this->moduleSettings['posixAccount_' . $typeId . '_minUID'][0]);
1120			$maxID = intval($this->moduleSettings['posixAccount_' . $typeId . '_maxUID'][0]);
1121		}
1122		if ($this->get_scope()=='host') {
1123			$minID = intval($this->moduleSettings['posixAccount_' . $typeId . '_minMachine'][0]);
1124			$maxID = intval($this->moduleSettings['posixAccount_' . $typeId . '_maxMachine'][0]);
1125		}
1126		$uids = $this->getUIDs($typeId);
1127		if ($this->attributes['uidNumber'][0]=='') {
1128			// No id-number given
1129			if (!isset($this->orig['uidNumber'][0]) || ($this->orig['uidNumber'][0] == '')) {
1130				// new account -> we have to find a free id-number
1131				$newUID = $this->getNextUIDs(1, $errors, $typeId);
1132				if (is_array($newUID)) {
1133					$this->attributes['uidNumber'][0] = $newUID[0];
1134				}
1135				else {
1136					$errors[] = $this->messages['uidNumber'][3];
1137				}
1138			}
1139			else $this->attributes['uidNumber'][0] = $this->orig['uidNumber'][0];
1140			// old account -> return id-number which has been used
1141		}
1142		else {
1143			// check manual ID
1144			if ($this->getAccountContainer()->isNewAccount || !isset($this->orig['uidNumber'][0]) || ($this->orig['uidNumber'][0] != $this->attributes['uidNumber'][0])) {
1145				// check range
1146				if (($this->get_scope() == 'user') && (!isset($this->moduleSettings['posixAccount_' . $typeId . '_uidGeneratorUsers']) || ($this->moduleSettings['posixAccount_' . $typeId . '_uidGeneratorUsers'][0] == 'range'))) {
1147					if (!is_numeric($this->attributes['uidNumber'][0]) || ($this->attributes['uidNumber'][0] < $minID) || ($this->attributes['uidNumber'][0] > $maxID)) {
1148						$errors[] = array('ERROR', _('ID-Number'), sprintf(_('Please enter a value between %s and %s!'), $minID, $maxID));
1149					}
1150				}
1151				if (($this->get_scope() == 'host') && (!isset($this->moduleSettings['posixAccount_' . $typeId . '_uidGeneratorHosts']) || ($this->moduleSettings['posixAccount_' . $typeId . '_uidGeneratorHosts'][0] == 'range'))) {
1152					if (!is_numeric($this->attributes['uidNumber'][0]) || ($this->attributes['uidNumber'][0] < $minID) || ($this->attributes['uidNumber'][0] > $maxID)) {
1153						$errors[] = array('ERROR', _('ID-Number'), sprintf(_('Please enter a value between %s and %s!'), $minID, $maxID));
1154					}
1155				}
1156				// id-number is in use and account is a new account
1157				if ((in_array($this->attributes['uidNumber'][0], $uids)) && !isset($this->orig['uidNumber'][0])) $errors[] = $this->messages['uidNumber'][3];
1158				// id-number is in use, account is existing account and id-number is not used by itself
1159				if ((in_array($this->attributes['uidNumber'][0], $uids)) && isset($this->orig['uidNumber'][0]) && ($this->orig['uidNumber'][0] != $this->attributes['uidNumber'][0]) ) {
1160					$errors[] = $this->messages['uidNumber'][3];
1161					$this->attributes['uidNumber'][0] = $this->orig['uidNumber'][0];
1162				}
1163			}
1164		}
1165		// Create automatic useraccount with number if original user already exists
1166		// Reset name to original name if new name is in use
1167		// Set username back to original name if new username is in use
1168		if ($this->userNameExists($this->attributes['uid'][0], $typeId) && isset($this->orig['uid'][0]) && ($this->orig['uid'][0]!='')) {
1169			$this->attributes['uid'][0] = $this->orig['uid'][0];
1170		}
1171		else {
1172			// Change uid to a new uid until a free uid is found
1173			while ($this->userNameExists($this->attributes['uid'][0], $typeId)) {
1174				$this->attributes['uid'][0] = $this->getNextUserName($this->attributes['uid'][0], array_keys($this->getAccountContainer()->getAccountModules()));
1175			}
1176		}
1177		// Show warning if LAM has changed username
1178		if ($this->attributes['uid'][0] != trim($_POST['uid'])) {
1179			$userNames = $this->getUserNames($typeId);
1180			if ($this->get_scope() == 'user') {
1181				$error = $this->messages['uid'][5];
1182				$error[] = array(htmlspecialchars($userNames[trim($_POST['uid'])]));
1183				$errors[] = $error;
1184			}
1185			if ($this->get_scope() == 'host') {
1186				$error = $this->messages['uid'][6];
1187				$error[] = array(htmlspecialchars($userNames[trim($_POST['uid'])]));
1188				$errors[] = $error;
1189			}
1190		}
1191		if (($this->get_scope() == 'user') && !get_preg($this->attributes['uid'][0], 'username')) {
1192			// Check if Username contains only valid characters
1193			$errors[] = $this->messages['uid'][2];
1194		}
1195		if ($this->get_scope() == 'host') {
1196			// Check if Hostname contains only valid characters
1197			if (!get_preg($this->attributes['uid'][0], 'hostname')) {
1198				$errors[] = $this->messages['uid'][4];
1199			}
1200			if (!isset($this->attributes[$homedirAttrName][0])) {
1201				$this->attributes[$homedirAttrName][0] = '/dev/null';
1202			}
1203			if (!isset($this->attributes['loginShell'][0])) {
1204				$this->attributes['loginShell'][0] = '/bin/false';
1205			}
1206		}
1207		$attributeList = array($homedirAttrName);
1208		if (!$this->isBooleanConfigOptionSet('posixAccount_' . $typeId . '_hidegecos')) {
1209			$attributeList[] = 'gecos';
1210		}
1211		for ($i = 0; $i < sizeof($attributeList); $i++) {
1212			if (isset($this->attributes[$attributeList[$i]][0])) {
1213				$value = $this->attributes[$attributeList[$i]][0];
1214				$replacedValue = $this->checkASCII($value);
1215				if ($value != $replacedValue) {
1216					$this->attributes[$attributeList[$i]][0] = $replacedValue;
1217					$errors[] = array('WARN', $attributeList[$i], _('Changed value because only ASCII characters are allowed.'));
1218				}
1219			}
1220		}
1221		if ($this->get_scope() == 'user') {
1222			// set SASL password for new and renamed users
1223			if (!empty($this->attributes['uid'][0]) && !empty($this->moduleSettings['posixAccount_pwdHash'][0])
1224					&& ($this->moduleSettings['posixAccount_pwdHash'][0] === 'SASL')
1225					&& ($this->getAccountContainer()->isNewAccount || ($this->attributes['uid'][0] != $this->orig['uid'][0]))) {
1226				$this->attributes[$this->getPasswordAttrName($modules)][0] = '{SASL}' . $this->attributes['uid'][0];
1227			}
1228			// set K5KEY password for new users
1229			if (!empty($this->moduleSettings['posixAccount_pwdHash'][0]) && ($this->moduleSettings['posixAccount_pwdHash'][0] === 'K5KEY')) {
1230				$this->attributes[$this->getPasswordAttrName($modules)][0] = pwd_hash('x', true, $this->moduleSettings['posixAccount_pwdHash'][0]);
1231			}
1232		}
1233		if (isset($_POST['posixAccount_createGroup'])
1234			&& !$this->isBooleanConfigOptionSet('posixAccount_' . $typeId . '_hideCreateGroup')
1235			&& ($this->get_scope() == 'user')
1236			&& $this->getAccountContainer()->isNewAccount && get_preg($this->attributes['uid'][0], 'username')) {
1237			$groupType = $this->getPosixGroupType();
1238			$sessionKey = 'TMP' . getRandomNumber();
1239			$accountContainerTmp = new accountContainer($groupType, $sessionKey);
1240			$_SESSION[$sessionKey] = &$accountContainerTmp;
1241			$accountContainerTmp->new_account();
1242			$posixGroupModule = $accountContainerTmp->getAccountModule('posixGroup');
1243			$nextGid = $posixGroupModule->getNextGIDs(1, $errors, $groupType);
1244			if ($nextGid !== null) {
1245				$newGroupName = $this->attributes['uid'][0];
1246				$dnNewGroup = 'cn=' . $newGroupName . ',' . $groupType->getSuffix();
1247				$attributesNewGroup = array(
1248					'cn' => array($newGroupName),
1249					'gidNumber' => $nextGid[0],
1250					'objectClass' => array('posixGroup'),
1251				);
1252				$newGroupSuccess = @ldap_add(getLDAPServerHandle(), $dnNewGroup, $attributesNewGroup);
1253				if ($newGroupSuccess) {
1254					$errors[] = array('INFO', _('Created new group.'), htmlspecialchars($newGroupName));
1255					$this->attributes['gidNumber'][0] = $nextGid[0];
1256					$this->groupCache = null;
1257				}
1258				else {
1259					$errors[] = array('ERROR', _('Unable to create new group.'), getDefaultLDAPErrorString(getLDAPServerHandle()));
1260				}
1261			}
1262		}
1263		// Return error-messages
1264		return $errors;
1265	}
1266
1267	/**
1268	 * Returns the first found group type that contains posixGroup.
1269	 *
1270	 * @return ConfiguredType|null type
1271	 */
1272	private function getPosixGroupType() {
1273		$typeManager = new TypeManager();
1274		$groupTypes = $typeManager->getConfiguredTypesForScope('group');
1275		foreach ($groupTypes as $groupType) {
1276			$modules = $groupType->getModules();
1277			if (in_array('posixGroup', $modules)) {
1278				return $groupType;
1279			}
1280		}
1281		return null;
1282	}
1283
1284	/**
1285	* Checks if an attribute contains only ASCII characters and replaces invalid characters.
1286	*
1287	* @param string $attribute attribute value
1288	* @return string attribute value with replaced non-ASCII characters
1289	*/
1290	function checkASCII($attribute) {
1291		if ($attribute == null) {
1292			return '';
1293		}
1294		// replace special characters
1295		$attribute = str_replace(array_keys($this->umlautReplacements), array_values($this->umlautReplacements), $attribute);
1296		// remove remaining UTF-8 characters
1297		for ($c = 0; $c < strlen($attribute); $c++) {
1298			if (ord($attribute[$c]) > 127) {
1299				$attribute = str_replace($attribute[$c], "", $attribute);
1300				$c--;
1301			}
1302		}
1303		return $attribute;
1304	}
1305
1306	/**
1307	* Processes user input of the group selection page.
1308	* It checks if all input values are correct and updates the associated LDAP attributes.
1309	*
1310	* @return array list of info/error messages
1311	*/
1312	function process_group() {
1313		$typeId = $this->getAccountContainer()->get_type()->getId();
1314		// Unix groups
1315		if ($this->isBooleanConfigOptionSet('posixGroup_autoSyncGon')) {
1316			$this->syncGonToGroups();
1317		}
1318		elseif (!$this->isBooleanConfigOptionSet('posixAccount_' . $typeId . '_hideposixGroups')) {
1319			if (isset($_POST['addgroups']) && isset($_POST['addgroups_button'])) { // Add groups to list
1320				// add new group
1321				$this->groups = @array_merge($this->groups, $_POST['addgroups']);
1322			}
1323			elseif (isset($_POST['removegroups']) && isset($_POST['removegroups_button'])) { // remove groups from list
1324				$this->groups = array_delete($_POST['removegroups'], $this->groups);
1325			}
1326		}
1327		// group of names
1328		if (self::areGroupOfNamesActive() && !$this->isBooleanConfigOptionSet('posixAccount_' . $typeId . '_hidegon')) {
1329			if (isset($_POST['addgons']) && isset($_POST['addgons_button'])) { // Add groups to list
1330				// add new group
1331				$this->gonList = @array_merge($this->gonList, $_POST['addgons']);
1332			}
1333			elseif (isset($_POST['removegons']) && isset($_POST['removegons_button'])) { // remove groups from list
1334				$this->gonList = array_delete($_POST['removegons'], $this->gonList);
1335			}
1336		}
1337		// sync Unix to GoN
1338		if (isset($_POST['form_subpage_posixAccount_group_syncU2GON'])) {
1339			$this->manualSyncUnixToGon($typeId);
1340		}
1341		// sync GoN to Unix
1342		if (isset($_POST['form_subpage_posixAccount_group_syncGON2U'])) {
1343			$this->manualSyncGonToUnix($this->getAccountContainer()->get_type());
1344		}
1345		// sync Windows to Unix
1346		if (isset($_POST['form_subpage_posixAccount_group_syncWin2U'])) {
1347			$this->manualSyncWindowsToUnix($this->getAccountContainer()->get_type());
1348		}
1349		return array();
1350	}
1351
1352	/**
1353	 * Syncs the Unix groups to group of names.
1354	 *
1355	 * @param string $typeId type ID
1356	 */
1357	private function manualSyncUnixToGon($typeId) {
1358		$allGons = $this->findGroupOfNames();
1359		$namesToIgnore = array();
1360		if (!empty($this->moduleSettings['posixAccount_' . $typeId . '_syncGroupsExclusions'])) {
1361			$namesToIgnore = $this->moduleSettings['posixAccount_' . $typeId . '_syncGroupsExclusions'];
1362			array_map('trim', $namesToIgnore);
1363		}
1364		// remove all groups that are not in Unix
1365		if (isset($_POST['syncDeleteGroups']) && ($_POST['syncDeleteGroups'] == 'on')) {
1366			$toDelete = array();
1367			foreach ($this->gonList as $currentGon) {
1368				$gonName = $this->getGonName($currentGon, $allGons);
1369				if (in_array($gonName, $namesToIgnore) || in_array($gonName, $this->groups)) {
1370					continue;
1371				}
1372				$toDelete[] = $currentGon;
1373			}
1374			$this->gonList = array_delete($toDelete, $this->gonList);
1375		}
1376		// add groups that are not yet in groups of names
1377		foreach ($this->groups as $currentName) {
1378			if (in_array($currentName, $namesToIgnore)) {
1379				continue;
1380			}
1381			$found = false;
1382			foreach ($this->gonList as $currentGon) {
1383				if ($currentName == $this->getGonName($currentGon, $allGons)) {
1384					$found = true;
1385					break;
1386				}
1387			}
1388			if ($found) {
1389				continue;
1390			}
1391			foreach ($allGons as $currentGon) {
1392				if ($currentName == $this->getGonName($currentGon['dn'], $allGons)) {
1393					$this->gonList[] = $currentGon['dn'];
1394					break;
1395				}
1396			}
1397		}
1398	}
1399
1400	/**
1401	 * Syncs the group of names to Unix groups.
1402	 *
1403	 * @param ConfiguredType $type type
1404	 */
1405	private function manualSyncGonToUnix($type) {
1406		$allGons = $this->findGroupOfNames();
1407		$modules = $type->getModules();
1408		$allGroups = $this->findGroups($modules);
1409		foreach ($allGroups as $index => $groupData) {
1410			$allGroups[$index] = $groupData[1];
1411		}
1412		$namesToIgnore = array();
1413		if (!empty($this->moduleSettings['posixAccount_' . $type->getId() . '_syncGroupsExclusions'])) {
1414			$namesToIgnore = $this->moduleSettings['posixAccount_' . $type->getId() . '_syncGroupsExclusions'];
1415			array_map('trim', $namesToIgnore);
1416		}
1417		// remove all groups that are not in group of names
1418		if (isset($_POST['syncDeleteGroups']) && ($_POST['syncDeleteGroups'] == 'on')) {
1419			$toDelete = array();
1420			foreach ($this->groups as $currentName) {
1421				if (in_array($currentName, $namesToIgnore)) {
1422					continue;
1423				}
1424				$found = false;
1425				foreach ($this->gonList as $currentGon) {
1426					$gonName = $this->getGonName($currentGon, $allGons);
1427					if ($gonName == $currentName) {
1428						$found = true;
1429						break;
1430					}
1431				}
1432				if (!$found) {
1433					$toDelete[] = $currentName;
1434				}
1435			}
1436			$this->groups = array_delete($toDelete, $this->groups);
1437		}
1438		// add groups that are not yet in Unix groups
1439		foreach ($this->gonList as $currentGon) {
1440			$gonName = $this->getGonName($currentGon, $allGons);
1441			if (in_array($gonName, $namesToIgnore)) {
1442				continue;
1443			}
1444			if (!in_array($gonName, $this->groups) && in_array($gonName, $allGroups)) {
1445				$this->groups[] = $gonName;
1446			}
1447		}
1448	}
1449
1450	/**
1451	 * Syncs the Windows to Unix groups.
1452	 *
1453	 * @param ConfiguredType $type type
1454	 */
1455	private function manualSyncWindowsToUnix($type) {
1456		$windowsGroups = $this->getAccountContainer()->getAccountModule('windowsUser')->getGroupList();
1457		$allWindowsGroups = searchLDAPByAttribute('gidNumber', '*', null, array('cn'), array('group'));
1458		$allGroups = $this->findGroups($modules);
1459		foreach ($allGroups as $index => $groupData) {
1460			$allGroups[$index] = $groupData[1];
1461		}
1462		$namesToIgnore = array();
1463		if (!empty($this->moduleSettings['posixAccount_' . $type->getId() . '_syncGroupsExclusions'])) {
1464			$namesToIgnore = $this->moduleSettings['posixAccount_' . $type->getId() . '_syncGroupsExclusions'];
1465			array_map('trim', $namesToIgnore);
1466		}
1467		// remove all groups that are not in Windows groups
1468		if (isset($_POST['syncDeleteGroups']) && ($_POST['syncDeleteGroups'] == 'on')) {
1469			$toDelete = array();
1470			foreach ($this->groups as $currentName) {
1471				if (in_array($currentName, $namesToIgnore)) {
1472					continue;
1473				}
1474				$found = false;
1475				foreach ($windowsGroups as $currentWindowsGroup) {
1476					$windowsGroupName = $this->getWindowsGroupName($allWindowsGroups, $currentWindowsGroup);
1477					if ($windowsGroupName == $currentName) {
1478						$found = true;
1479						break;
1480					}
1481				}
1482				if (!$found) {
1483					$toDelete[] = $currentName;
1484				}
1485			}
1486			$this->groups = array_delete($toDelete, $this->groups);
1487		}
1488		// add groups that are not yet in Unix groups
1489		foreach ($windowsGroups as $currentWindowsGroup) {
1490			$windowsGroupName = $this->getWindowsGroupName($allWindowsGroups, $currentWindowsGroup);
1491			if (in_array($windowsGroupName, $namesToIgnore)) {
1492				continue;
1493			}
1494			if (!in_array($windowsGroupName, $this->groups) && in_array($windowsGroupName, $allGroups)) {
1495				$this->groups[] = $windowsGroupName;
1496			}
1497		}
1498	}
1499
1500	/**
1501	 * Returns the cn of the given group of names.
1502	 *
1503	 * @param string $dn DN of group of names
1504	 * @param $allGons list of all group of names
1505	 * @return string cn value
1506	 */
1507	public function getGonName($dn, &$allGons) {
1508		if (!empty($allGons[$dn]['cn'][0])) {
1509			return $allGons[$dn]['cn'][0];
1510		}
1511		return extractRDNValue($dn);
1512	}
1513
1514	/**
1515	 * Returns the Windows group name.
1516	 *
1517	 * @param array $allWindowsGroups LDAP data of all Windows groups
1518	 * @param string $dn DN
1519	 */
1520	private function getWindowsGroupName(&$allWindowsGroups, $dn) {
1521		foreach ($allWindowsGroups as $data) {
1522			if ($data['dn'] == $dn) {
1523				return $data['cn'][0];
1524			}
1525		}
1526		return null;
1527	}
1528
1529	/**
1530	* Processes user input of the homedir check page.
1531	* It checks if all input values are correct and updates the associated LDAP attributes.
1532	*
1533	* @return array list of info/error messages
1534	*/
1535	function process_homedir() {
1536		$return = array();
1537		// get list of lamdaemon servers
1538		$lamdaemonServers = $_SESSION['config']->getConfiguredScriptServers();
1539		$modules = $this->getAccountContainer()->get_type()->getModules();
1540		$homeDirAttr = $this->getHomedirAttrName($modules);
1541		for ($i = 0; $i < sizeof($lamdaemonServers); $i++) {
1542			if (isset($_POST['form_subpage_' . get_class($this) . '_homedir_create_' . $i])) {
1543				$remote = new \LAM\REMOTE\Remote();
1544				$remote->connect($lamdaemonServers[$i]);
1545				$result = $remote->execute(
1546					implode(
1547						self::$SPLIT_DELIMITER,
1548						array(
1549							$this->attributes['uid'][0],
1550							"home",
1551							"add",
1552							$lamdaemonServers[$i]->getHomeDirPrefix() . $this->attributes[$homeDirAttr][0],
1553							"0".$_SESSION['config']->get_scriptRights(),
1554							$this->attributes['uidNumber'][0],
1555							$this->attributes['gidNumber'][0])
1556					));
1557				$remote->disconnect();
1558				// lamdaemon results
1559				if (!empty($result)) {
1560					$singleresult = explode(",", $result);
1561					if (is_array($singleresult)) {
1562						if (($singleresult[0] == 'ERROR') || ($singleresult[0] == 'WARN') || ($singleresult[0] == 'INFO')) {
1563							$return[] = $singleresult;
1564						}
1565					}
1566				}
1567			}
1568			elseif (isset($_POST['form_subpage_' . get_class($this) . '_homedir_delete_' . $i])) {
1569				$remote = new \LAM\REMOTE\Remote();
1570				$remote->connect($lamdaemonServers[$i]);
1571				$result = $remote->execute(
1572					implode(
1573						self::$SPLIT_DELIMITER,
1574						array(
1575							$this->attributes['uid'][0],
1576							"home",
1577							"rem",
1578							$lamdaemonServers[$i]->getHomeDirPrefix() . $this->attributes[$homeDirAttr][0],
1579							$this->attributes['uidNumber'][0]
1580						)
1581					));
1582				$remote->disconnect();
1583				// lamdaemon results
1584				if (!empty($result)) {
1585					$singleresult = explode(",", $result);
1586					if (is_array($singleresult)) {
1587						if (($singleresult[0] == 'ERROR') || ($singleresult[0] == 'WARN') || ($singleresult[0] == 'INFO')) {
1588							$return[] = $singleresult;
1589						}
1590					}
1591				}
1592			}
1593		}
1594		return $return;
1595	}
1596
1597	/**
1598	 * Returns the HTML meta data for the main account page.
1599	 *
1600	 * @return htmlElement HTML meta data
1601	 */
1602	function display_html_attributes() {
1603		$return = new htmlResponsiveRow();
1604		$this->checkForInvalidConfiguration($return);
1605		$modules = $this->getAccountContainer()->get_type()->getModules();
1606		$typeId = $this->getAccountContainer()->get_type()->getId();
1607		if (!$this->isOptional($modules) || $this->skipObjectClass() || (isset($this->attributes['objectClass']) && in_array('posixAccount', $this->attributes['objectClass']))) {
1608			$homeDirAttr = $this->getHomedirAttrName($modules);
1609			$groupList = $this->findGroups($modules); // list of all group names
1610			$groups = array();
1611			for ($i = 0; $i < sizeof($groupList); $i++) {
1612				$groups[$groupList[$i][1]] = $groupList[$i][0];
1613			}
1614			if (count($groups)==0) {
1615				$return->add(new htmlStatusMessage("ERROR", _('No Unix groups found in LDAP! Please create one first.')), 12);
1616				return $return;
1617			}
1618			$shelllist = $this->getShells(); // list of all valid shells
1619
1620			// set default values
1621			if (empty($this->attributes['uid'][0])) {
1622				if ($this->getAccountContainer()->getAccountModule('inetOrgPerson') != null) {
1623					// fill default value for user ID with first/last name
1624					$attrs = $this->getAccountContainer()->getAccountModule('inetOrgPerson')->getAttributes();
1625					$this->attributes['uid'][0] = $this->getUserNameSuggestion($attrs, $typeId);
1626					$firstSuggestion = $this->attributes['uid'][0];
1627					if (!empty($this->attributes['uid'][0]) && $this->userNameExists($this->attributes['uid'][0], $typeId)) {
1628						while ($this->userNameExists($this->attributes['uid'][0], $typeId)) {
1629							$this->attributes['uid'][0] = $this->getNextUserName($this->attributes['uid'][0], array_keys($this->getAccountContainer()->getAccountModules()));
1630						}
1631						$users = $this->getUserNames($typeId);
1632						$msg = new htmlStatusMessage($this->messages['uid'][5][0],
1633								$this->messages['uid'][5][1], $this->messages['uid'][5][2],
1634								array(htmlspecialchars($users[$firstSuggestion])));
1635						$return->add($msg, 12);
1636					}
1637				}
1638				elseif ($this->getAccountContainer()->getAccountModule('windowsUser') != null) {
1639					// fill default value for user ID with AD user name
1640					$attrs = $this->getAccountContainer()->getAccountModule('windowsUser')->getAttributes();
1641					if (!empty($attrs['userPrincipalName'][0])) {
1642						$parts = explode('@', $attrs['userPrincipalName'][0]);
1643						$this->attributes['uid'][0] = $parts[0];
1644					}
1645				}
1646			}
1647			if ($this->manageCn($modules) && (!isset($this->attributes['cn'][0]) || ($this->attributes['cn'][0] == ''))) {
1648				// set a default value for common name
1649				if (($this->get_scope() == 'host') && isset($_POST['uid'])) {
1650					if (substr($_POST['uid'], -1, 1) == '$') {
1651						$this->attributes['cn'][0] = substr($_POST['uid'], 0, strlen($_POST['uid']) - 1);
1652					}
1653					else {
1654						$this->attributes['cn'][0] = $_POST['uid'];
1655					}
1656				}
1657				elseif ($this->getAccountContainer()->getAccountModule('inetOrgPerson') != null) {
1658					$attrs = $this->getAccountContainer()->getAccountModule('inetOrgPerson')->getAttributes();
1659					if ($attrs['givenName'][0]) {
1660						$this->attributes['cn'][0] = $attrs['givenName'][0] . " " . $attrs['sn'][0];
1661					}
1662					elseif ($attrs['sn'][0]) {
1663						$this->attributes['cn'][0] = $attrs['sn'][0];
1664					}
1665					else {
1666						$this->attributes['cn'][0] = $_POST['uid'];
1667					}
1668				}
1669				elseif (isset($_POST['uid'])) {
1670					$this->attributes['cn'][0] = $_POST['uid'];
1671				}
1672			}
1673
1674			$userName = '';
1675			if (isset($this->attributes['uid'][0])) {
1676				$userName = $this->attributes['uid'][0];
1677			}
1678			$uidLabel = _("User name");
1679			if ($this->get_scope() == 'host') {
1680				$uidLabel = _("Host name");
1681			}
1682			$uidInput = new htmlResponsiveInputField($uidLabel, 'uid', $userName, 'uid');
1683			$uidInput->setRequired(true);
1684			$uidInput->setFieldMaxLength(100);
1685			$return->add($uidInput, 12);
1686			if ($this->manageCn($modules)) {
1687				$this->addMultiValueInputTextField($return, 'cn', _("Common name"));
1688			}
1689			$uidNumber = '';
1690			if (isset($this->attributes['uidNumber'][0])) {
1691				$uidNumber = $this->attributes['uidNumber'][0];
1692			}
1693			$uidNumberInput = new htmlResponsiveInputField(_('UID number'), 'uidNumber', $uidNumber, 'uidNumber');
1694			$uidNumberInput->setFieldMaxLength(20);
1695			$uidNumberInput->setValidationRule(htmlElement::VALIDATE_NUMERIC);
1696			$return->add($uidNumberInput, 12);
1697			if (!$this->isBooleanConfigOptionSet('posixAccount_' . $typeId . '_hidegecos')) {
1698				$gecos = '';
1699				if (isset($this->attributes['gecos'][0])) {
1700					$gecos = $this->attributes['gecos'][0];
1701				}
1702				$return->add(new htmlResponsiveInputField(_('Gecos'), 'gecos', $gecos, 'gecos'), 12);
1703			}
1704			$primaryGroup = array();
1705			if (isset($this->attributes['gidNumber'][0])) {
1706				$primaryGroup[] = $this->attributes['gidNumber'][0];
1707			}
1708			$gidNumberSelect = new htmlResponsiveSelect('gidNumber', $groups, $primaryGroup, _('Primary group'), 'gidNumber');
1709			$gidNumberSelect->setHasDescriptiveElements(true);
1710			$return->add($gidNumberSelect, 12);
1711
1712			if ($this->get_scope() == 'user') {
1713				// new Unix group with same name
1714				$posixGroupType = $this->getPosixGroupType();
1715				if ($this->getAccountContainer()->isNewAccount
1716					&& !$this->isBooleanConfigOptionSet('posixAccount_' . $typeId . '_hideCreateGroup')
1717					&& ($posixGroupType !== null)
1718					&& (!isset($this->attributes['uid'][0]) || !isset($groups[$this->attributes['uid'][0]]))) {
1719					$return->addLabel(new htmlOutputText('&nbsp;', false));
1720					$return->addField(new htmlButton('posixAccount_createGroup', _('Create group with same name')));
1721				}
1722				// additional groups
1723				if (!$this->isBooleanConfigOptionSet('posixAccount_' . $typeId . '_hidegon') || !$this->isBooleanConfigOptionSet('posixAccount_' . $typeId . '_hideposixGroups')) {
1724					$return->addLabel(new htmlOutputText(_('Additional groups')));
1725					$additionalGroups = new htmlGroup();
1726					$additionalGroups->addElement(new htmlAccountPageButton(get_class($this), 'group', 'open', _('Edit groups')));
1727					$additionalGroups->addElement(new htmlHelpLink('addgroup'));
1728					$return->addField($additionalGroups);
1729				}
1730				// home directory
1731				$homeDir = isset($this->attributes[$homeDirAttr][0]) ? $this->attributes[$homeDirAttr][0] : '';
1732				$homedirInput = new htmlResponsiveInputField(_('Home directory'), 'homeDirectory', $homeDir, 'homeDirectory');
1733				$homedirInput->setRequired(true);
1734				$return->add($homedirInput, 12);
1735				if (($_SESSION['config']->get_scriptPath() != null) && ($_SESSION['config']->get_scriptPath() != '')) {
1736					if ($this->getAccountContainer()->isNewAccount) {
1737						// get list of lamdaemon servers
1738						$lamdaemonServers = $_SESSION['config']->getConfiguredScriptServers();
1739						$homeDirLabel = new htmlOutputText(_('Create home directory'));
1740						$return->addLabel($homeDirLabel);
1741						$homeServerContainer = new htmlTable();
1742						for ($h = 0; $h < sizeof($lamdaemonServers); $h++) {
1743							$homeServerContainer->addElement(new htmlTableExtendedInputCheckbox('createhomedir_' . $h, in_array($lamdaemonServers[$h]->getServer(), $this->lamdaemonServers), $lamdaemonServers[$h]->getLabel(), null, false));
1744							if ($h === 0) {
1745								$homeDirHelp = new htmlHelpLink('createhomedir');
1746								$homeServerContainer->addElement($homeDirHelp);
1747							}
1748							$homeServerContainer->addNewLine();
1749						}
1750						$return->addField($homeServerContainer);
1751					}
1752					else {
1753						$return->addLabel(new htmlOutputText('&nbsp;', false));
1754						$return->addField(new htmlAccountPageButton(get_class($this), 'homedir', 'open', _('Check home directories')));
1755					}
1756				}
1757				$selectedShell = array();
1758				if (isset($this->attributes['loginShell'][0])) {
1759					$selectedShell = array($this->attributes['loginShell'][0]);
1760				}
1761				$return->add(new htmlResponsiveSelect('loginShell', $shelllist, $selectedShell, _('Login shell'), 'loginShell'), 12);
1762			}
1763			// password buttons
1764			if (checkIfWriteAccessIsAllowed($this->get_scope())
1765					&& isset($this->attributes[$this->getPasswordAttrName($modules)][0])
1766					&& $this->isPasswordManaged()) {
1767				$return->addLabel(new htmlOutputText(_('Password')));
1768				$pwdContainer = new htmlGroup();
1769				if (pwd_is_enabled($this->attributes[$this->getPasswordAttrName($modules)][0])) {
1770					$pwdContainer->addElement(new htmlButton('lockPassword', _('Lock password')));
1771				}
1772				else {
1773					$pwdContainer->addElement(new htmlButton('unlockPassword', _('Unlock password')));
1774				}
1775				$pwdContainer->addElement(new htmlButton('removePassword', _('Remove password')));
1776				$return->addField($pwdContainer);
1777			}
1778			// remove button
1779			if ($this->isOptional($modules) && !$this->skipObjectClass()) {
1780				$return->addVerticalSpacer('2rem');
1781				$remButton = new htmlButton('remObjectClass', _('Remove Unix extension'));
1782				$return->add($remButton, 12, 12, 12, 'text-center');
1783			}
1784		}
1785		else {
1786			// add button
1787			$return->add(new htmlButton('addObjectClass', _('Add Unix extension')), 12);
1788		}
1789		return $return;
1790	}
1791
1792	/**
1793	 * Checks if the configuration is valid and prints an error if not.
1794	 *
1795	 * @param htmlResponsiveRow $content content area
1796	 */
1797	private function checkForInvalidConfiguration(htmlResponsiveRow $content) {
1798		$typeId = $this->getAccountContainer()->get_type()->getId();
1799		if ($this->get_scope() == 'user') {
1800			$generatorOption = 'posixAccount_' . $typeId . '_uidGeneratorUsers';
1801		}
1802		else {
1803			$generatorOption = 'posixAccount_' . $typeId . '_uidGeneratorHosts';
1804		}
1805		if (empty($this->moduleSettings[$generatorOption])) {
1806			$message = new htmlStatusMessage('ERROR', _('Invalid configuration detected. Please edit your server profile (module settings) and fill all required fields.'));
1807			$content->add($message, 12);
1808		}
1809	}
1810
1811	/**
1812	* Displays the delete homedir option for the delete page.
1813	*
1814	* @return htmlElement meta HTML code
1815	*/
1816	function display_html_delete() {
1817		$return = new htmlResponsiveRow();
1818		if ($this->get_scope() == 'user' && ($_SESSION['config']->get_scriptPath() != null)) {
1819			$return->add(new htmlResponsiveInputCheckbox('deletehomedir', true, _('Delete home directory'), 'deletehomedir'), 12);
1820		}
1821		$typeManager = new TypeManager();
1822		$sudoTypes = $typeManager->getConfiguredTypesForScope('sudo');
1823		if (($this->get_scope() == 'user') && !empty($sudoTypes)) {
1824			$return->add(new htmlResponsiveInputCheckbox('deleteSudoers', true, _('Delete sudo rights'), 'deleteSudoers'), 12);
1825		}
1826		return $return;
1827	}
1828
1829	/**
1830	* Displays the group selection.
1831	*
1832	* @return htmlElement meta HTML code
1833	*/
1834	function display_html_group() {
1835		$return = new htmlResponsiveRow();
1836		$modules = $this->getAccountContainer()->get_type()->getModules();
1837		$typeId = $this->getAccountContainer()->get_type()->getId();
1838		$showUnix = !$this->isBooleanConfigOptionSet('posixAccount_' . $typeId . '_hideposixGroups');
1839		$autoSyncGon = $this->isBooleanConfigOptionSet('posixGroup_autoSyncGon');
1840		if ($showUnix) {
1841			// load list with all groups
1842			$groups = $this->findGroups($modules);
1843			for ($i = 0; $i < sizeof($groups); $i++) {
1844				$groups[$i] = $groups[$i][1];
1845			}
1846			// remove groups the user is member of from grouplist
1847			$groups = array_delete($this->groups, $groups);
1848			// Remove primary group from grouplist
1849			$group = $this->getGroupName($this->attributes['gidNumber'][0]);
1850			$groups = array_flip($groups);
1851			unset ($groups[$group]);
1852			$groups = array_flip($groups);
1853
1854			$unixContainer = new htmlTable();
1855			$unixContainer->alignment = htmlElement::ALIGN_TOP;
1856			$unixContainer->addElement(new htmlSubTitle(_("Unix groups")), true);
1857			if ($autoSyncGon) {
1858				$this->syncGonToGroups();
1859				foreach ($this->groups as $group) {
1860					$unixContainer->addElement(new htmlOutputText($group), true);
1861				}
1862			}
1863			else {
1864				$unixContainer->addElement(new htmlOutputText(_("Selected groups")));
1865				$unixContainer->addElement(new htmlOutputText(''));
1866				$unixContainer->addElement(new htmlOutputText(_("Available groups")));
1867				$unixContainer->addNewLine();
1868
1869				$remSelect = new htmlSelect('removegroups', $this->groups, null, 15);
1870				$remSelect->setMultiSelect(true);
1871				$remSelect->setTransformSingleSelect(false);
1872				$unixContainer->addElement($remSelect);
1873				$buttonContainer = new htmlTable();
1874				$buttonContainer->addElement(new htmlButton('addgroups_button', 'back.gif', true), true);
1875				$buttonContainer->addElement(new htmlButton('removegroups_button', 'forward.gif', true), true);
1876				$buttonContainer->addElement(new htmlHelpLink('addgroup'));
1877				$unixContainer->addElement($buttonContainer);
1878				$addSelect = new htmlSelect('addgroups', $groups, null, 15);
1879				$addSelect->setMultiSelect(true);
1880				$addSelect->setTransformSingleSelect(false);
1881				$unixContainer->addElement($addSelect, true);
1882			}
1883
1884			$return->add($unixContainer, 12);
1885			$return->addVerticalSpacer('3rem');
1886		}
1887
1888		$showGon = self::areGroupOfNamesActive() && !$this->isBooleanConfigOptionSet('posixAccount_' . $typeId . '_hidegon');
1889		if ($showGon) {
1890			$gons = $this->findGroupOfNames();
1891
1892			$gonContainer = new htmlTable();
1893			$gonContainer->alignment = htmlElement::ALIGN_TOP;
1894			$gonContainer->addElement(new htmlSubTitle(_("Groups of names")), true);
1895			$gonContainer->addElement(new htmlOutputText(_("Selected groups")));
1896			$gonContainer->addElement(new htmlOutputText(''));
1897			$gonContainer->addElement(new htmlOutputText(_("Available groups")));
1898			$gonContainer->addNewLine();
1899
1900			$selectedGons = array();
1901			for ($i = 0; $i < sizeof($this->gonList); $i++) {
1902				if (isset($gons[$this->gonList[$i]])) {
1903					$selectedGons[getAbstractDN($this->gonList[$i])] = $this->gonList[$i];
1904				}
1905			}
1906			uksort($selectedGons, 'compareDN');
1907			$availableGons = array();
1908			foreach ($gons as $dn => $attr) {
1909				if (!in_array($dn, $this->gonList)) {
1910					$availableGons[getAbstractDN($dn)] = $dn;
1911				}
1912			}
1913			uksort($availableGons, 'compareDN');
1914
1915			$remGonSelect = new htmlSelect('removegons', $selectedGons, null, 15);
1916			$remGonSelect->setMultiSelect(true);
1917			$remGonSelect->setTransformSingleSelect(false);
1918			$remGonSelect->setHasDescriptiveElements(true);
1919			$remGonSelect->setSortElements(false);
1920			$remGonSelect->setRightToLeftTextDirection(true);
1921			$gonContainer->addElement($remGonSelect);
1922			$buttonGonContainer = new htmlTable();
1923			$buttonGonContainer->addElement(new htmlButton('addgons_button', 'back.gif', true), true);
1924			$buttonGonContainer->addElement(new htmlButton('removegons_button', 'forward.gif', true), true);
1925			$buttonGonContainer->addElement(new htmlHelpLink('addgroup'));
1926			$gonContainer->addElement($buttonGonContainer);
1927			$addGonSelect = new htmlSelect('addgons', $availableGons, null, 15);
1928			$addGonSelect->setMultiSelect(true);
1929			$addGonSelect->setHasDescriptiveElements(true);
1930			$addGonSelect->setTransformSingleSelect(false);
1931			$addGonSelect->setSortElements(false);
1932			$addGonSelect->setRightToLeftTextDirection(true);
1933			$gonContainer->addElement($addGonSelect);
1934			$gonContainer->addNewLine();
1935			$return->add($gonContainer, 12);
1936			$return->addVerticalSpacer('3rem');
1937		}
1938
1939		$showGonSync = $showGon && !$autoSyncGon;
1940		$showUnixSync = $showUnix && !$this->isBooleanConfigOptionSet('posixAccount_' . $typeId . '_syncGroups');
1941		$moduleList = $this->getAccountContainer()->get_type()->getModules();
1942		$showWindowsSync = $this->isWindows($moduleList);
1943		if ($showUnixSync && ($showGonSync || $showWindowsSync)) {
1944			$return->add(new htmlSubTitle(_('Sync groups')), 12);
1945			$return->add(new htmlResponsiveInputCheckbox('syncDeleteGroups', true, _('Delete non-matching entries')), 12);
1946			$return->addVerticalSpacer('1rem');
1947			if ($showGonSync) {
1948				$syncButtons = new htmlGroup();
1949				$u2gonButton = new htmlAccountPageButton(get_class($this), 'group', 'syncU2GON', _('Sync Unix to group of names'));
1950				$u2gonButton->setIconClass('unixButton');
1951				$syncButtons->addElement($u2gonButton);
1952				$syncButtons->addElement(new htmlSpacer('2rem', null));
1953				$gon2uButton = new htmlAccountPageButton(get_class($this), 'group', 'syncGON2U', _('Sync group of names to Unix'));
1954				$gon2uButton->setIconClass('groupButton');
1955				$syncButtons->addElement($gon2uButton);
1956				$return->add($syncButtons, 12, 12, 12, 'text-center');
1957				if ($showWindowsSync) {
1958					$syncButtons->addElement(new htmlSpacer('2rem', null));
1959				}
1960			}
1961			if ($showWindowsSync) {
1962				$syncButtons = new htmlGroup();
1963				$gon2uButton = new htmlAccountPageButton(get_class($this), 'group', 'syncWin2U', _('Sync Windows to Unix'));
1964				$gon2uButton->setIconClass('sambaButton');
1965				$syncButtons->addElement($gon2uButton);
1966				$return->add($syncButtons, 12, 12, 12, 'text-center');
1967			}
1968		}
1969
1970		$return->addVerticalSpacer('2rem');
1971		$backButton = new htmlAccountPageButton(get_class($this), 'attributes', 'back', _('Back'));
1972		$return->add($backButton, 12);
1973		return $return;
1974	}
1975
1976	/**
1977	* Displays the delete homedir option for the homedir page.
1978	*
1979	* @return htmlElement meta HTML code
1980	*/
1981	function display_html_homedir() {
1982		$modules = $this->getAccountContainer()->get_type()->getModules();
1983		$homeDirAttr = $this->getHomedirAttrName($modules);
1984		$return = new htmlResponsiveRow();
1985		$return->addLabel(new htmlOutputText(_('Home directory')));
1986		$return->addField(new htmlOutputText($this->attributes[$homeDirAttr][0]));
1987		$return->addVerticalSpacer('2rem');
1988		// get list of lamdaemon servers
1989		$lamdaemonServers = $_SESSION['config']->getConfiguredScriptServers();
1990		for ($i = 0; $i < sizeof($lamdaemonServers); $i++) {
1991			$label = $lamdaemonServers[$i]->getLabel();
1992			$remote = new \LAM\REMOTE\Remote();
1993			$remote->connect($lamdaemonServers[$i]);
1994			$result = $remote->execute(
1995				implode(
1996					self::$SPLIT_DELIMITER,
1997					array(
1998						$this->attributes['uid'][0],
1999						"home",
2000						"check",
2001						$lamdaemonServers[$i]->getHomeDirPrefix() . $this->attributes[$homeDirAttr][0])
2002					));
2003			$remote->disconnect();
2004			// lamdaemon results
2005			if (!empty($result)) {
2006				$returnValue = trim($result);
2007				if ($returnValue == 'ok') {
2008					$return->addLabel(new htmlOutputText($label));
2009					$okGroup = new htmlGroup();
2010					$okGroup->addElement(new htmlImage('../../graphics/pass.png', 16, 16));
2011					$okGroup->addElement(new htmlSpacer('5px', null));
2012					$okGroup->addElement(new htmlAccountPageButton(get_class($this), 'homedir', 'delete_' . $i, _('Delete')));
2013					$return->addField($okGroup);
2014				}
2015				elseif ($returnValue == 'missing') {
2016					$return->addLabel(new htmlOutputText($label));
2017					$failGroup = new htmlGroup();
2018					$failGroup->addElement(new htmlImage('../../graphics/fail.png', 16, 16));
2019					$failGroup->addElement(new htmlSpacer('5px', null));
2020					$failGroup->addElement(new htmlAccountPageButton(get_class($this), 'homedir', 'create_' . $i, _('Create')));
2021					$return->addField($failGroup);
2022				}
2023				elseif (trim($returnValue) != '') {
2024					$messageParams = explode(",", $returnValue);
2025					if (isset($messageParams[2])) {
2026						$message = new htmlStatusMessage($messageParams[0], htmlspecialchars($messageParams[1]), htmlspecialchars($messageParams[2]));
2027					}
2028					elseif (($messageParams[0] == 'ERROR') || ($messageParams[0] == 'WARN') || ($messageParams[0] == 'INFO')) {
2029						$message = new htmlStatusMessage($messageParams[0], htmlspecialchars($messageParams[1]));
2030					}
2031					else {
2032						$message = new htmlStatusMessage('WARN', htmlspecialchars($messageParams[0]));
2033					}
2034					$return->add($message, 12);
2035				}
2036			}
2037		}
2038		$return->addVerticalSpacer('2rem');
2039		$return->add(new htmlAccountPageButton(get_class($this), 'attributes', 'back', _('Back')), 12, 12, 12, 'text-center');
2040		return $return;
2041	}
2042
2043	/**
2044	* {@inheritDoc}
2045	*/
2046	function get_profileOptions($typeId) {
2047		$return = new htmlResponsiveRow();
2048		$typeManager = new TypeManager();
2049		$modules = $typeManager->getConfiguredType($typeId)->getModules();
2050		$groupList = $this->findGroups($modules);
2051		$groups = array();
2052		for ($i = 0; $i < sizeof($groupList); $i++) {
2053			$groups[] = $groupList[$i][1];
2054		}
2055		if ($this->get_scope() == 'user') {
2056			$shelllist = $this->getShells(); // list of all valid shells
2057			// primary Unix group
2058			$return->add(new htmlResponsiveSelect('posixAccount_primaryGroup', $groups, array(), _('Primary group'), 'gidNumber'), 12);
2059			// additional group memberships
2060			$addGroupSelect = new htmlResponsiveSelect('posixAccount_additionalGroup', $groups, array(), _('Additional groups'), 'addgroup', 10);
2061			$addGroupSelect->setMultiSelect(true);
2062			$addGroupSelect->setTransformSingleSelect(false);
2063			$return->add($addGroupSelect, 12);
2064			// group of names
2065			if (self::areGroupOfNamesActive()) {
2066				$gons = $this->findGroupOfNames();
2067				$gonList = array();
2068				foreach ($gons as $dn => $attr) {
2069					$gonList[$attr['cn'][0]] = $dn;
2070				}
2071				$gonSelect = new htmlResponsiveSelect('posixAccount_gon', $gonList, array(), _('Groups of names'), 'addgroup', 10);
2072				$gonSelect->setHasDescriptiveElements(true);
2073				$gonSelect->setMultiSelect(true);
2074				$gonSelect->setTransformSingleSelect(false);
2075				$return->add($gonSelect, 12);
2076			}
2077			// common name
2078			if ($this->manageCn($modules)) {
2079				$return->add(new htmlResponsiveInputField(_('Common name'), 'posixAccount_cn', '', 'cn'), 12);
2080			}
2081			// home directory
2082			$return->add(new htmlResponsiveInputField(_('Home directory'), 'posixAccount_homeDirectory', '/home/$user', 'homeDirectory'), 12);
2083			// login shell
2084			$return->add(new htmlResponsiveSelect('posixAccount_loginShell', $shelllist, array("/bin/bash"), _('Login shell'), 'loginShell'), 12);
2085			// lamdaemon settings
2086			if ($_SESSION['config']->get_scriptPath() != null) {
2087				$return->add(new htmlSubTitle(_('Create home directory')), 12);
2088				$lamdaemonServers = $_SESSION['config']->getConfiguredScriptServers();
2089				for ($h = 0; $h < sizeof($lamdaemonServers); $h++) {
2090					$server = $lamdaemonServers[$h]->getServer();
2091					$label = $lamdaemonServers[$h]->getLabel();
2092					$return->add(new htmlResponsiveInputCheckbox('posixAccount_createHomedir_' . $h, in_array($server, $this->lamdaemonServers), $label, 'createhomedir', false), 12);
2093				}
2094			}
2095		}
2096		elseif ($this->get_scope() == 'host') {
2097			// primary Unix group
2098			$return->add(new htmlResponsiveSelect('posixAccount_primaryGroup', $groups, array(), _('Primary group'), 'gidNumber'), 12);
2099		}
2100		if ($this->isOptional($modules)) {
2101			$return->add(new htmlResponsiveInputCheckbox('posixAccount_addExt', false, _('Automatically add this extension'), 'autoAdd'), 12);
2102		}
2103		return $return;
2104	}
2105
2106	/**
2107	* Loads the values of an account profile into internal variables.
2108	*
2109	* @param array $profile hash array with profile values (identifier => value)
2110	*/
2111	function load_profile($profile) {
2112		// profile mappings in meta data
2113		parent::load_profile($profile);
2114		$modules = $this->getAccountContainer()->get_type()->getModules();
2115		// cn
2116		if ($this->manageCn($modules) && !empty($profile['posixAccount_cn'][0])) {
2117			$this->attributes['cn'][0] = $profile['posixAccount_cn'][0];
2118		}
2119		// home directory
2120		$homeDirAttr = $this->getHomedirAttrName($modules);
2121		if (!empty($profile['posixAccount_homeDirectory'][0])) {
2122			$this->attributes[$homeDirAttr][0] = $profile['posixAccount_homeDirectory'][0];
2123		}
2124		// special profile options
2125		// GID
2126		if (isset($profile['posixAccount_primaryGroup'][0])) {
2127			$gid = $this->getGID($profile['posixAccount_primaryGroup'][0]);
2128			if ($gid != null) {
2129				$this->attributes['gidNumber'][0] = $gid;
2130			}
2131		}
2132		// other group memberships
2133		if (isset($profile['posixAccount_additionalGroup'][0])) {
2134			$this->groups = $profile['posixAccount_additionalGroup'];
2135		}
2136		// group of names
2137		if (isset($profile['posixAccount_gon'][0])) {
2138			$this->gonList = $profile['posixAccount_gon'];
2139		}
2140		// lamdaemon
2141		if (($this->get_scope() == 'user') && $this->getAccountContainer()->isNewAccount) {
2142			$lamdaemonServers = $_SESSION['config']->getConfiguredScriptServers();
2143			$this->lamdaemonServers = array();
2144			for ($h = 0; $h < sizeof($lamdaemonServers); $h++) {
2145				if (isset($profile['posixAccount_createHomedir_' . $h][0]) && ($profile['posixAccount_createHomedir_' . $h][0] == 'true')) {
2146					$this->lamdaemonServers[] = $lamdaemonServers[$h]->getServer();
2147				}
2148			}
2149		}
2150		// add extension
2151		if (isset($profile['posixAccount_addExt'][0]) && ($profile['posixAccount_addExt'][0] == "true")) {
2152			if (!$this->skipObjectClass() && !in_array('posixAccount', $this->attributes['objectClass'])) {
2153				$this->attributes['objectClass'][] = 'posixAccount';
2154			}
2155		}
2156	}
2157
2158	/**
2159	 * {@inheritDoc}
2160	 * @see baseModule::get_pdfFields()
2161	 */
2162	public function get_pdfFields($typeId) {
2163		$fields = parent::get_pdfFields($typeId);
2164		$typeManager = new TypeManager();
2165		$modules = $typeManager->getConfiguredType($typeId)->getModules();
2166		if ($this->manageCn($modules)) {
2167			$fields['cn'] = _('Common name');
2168		}
2169		if (!$this->isBooleanConfigOptionSet('posixAccount_' . $typeId . '_hidegecos')) {
2170			$fields['gecos'] = _('Gecos');
2171		}
2172		return $fields;
2173	}
2174
2175	/**
2176	 * {@inheritDoc}
2177	 * @see baseModule::get_pdfEntries()
2178	 */
2179	function get_pdfEntries($pdfKeys, $typeId) {
2180		$uidLabel = _('User name');
2181		if ($this->get_scope() == 'host') {
2182			$uidLabel = _('Host name');
2183		}
2184		$additionalGroups = array();
2185		if (!empty($this->groups)) {
2186			$additionalGroups = $this->groups;
2187			natcasesort($additionalGroups);
2188		}
2189		$modules = $this->getAccountContainer()->get_type()->getModules();
2190		$homeDirAttr = $this->getHomedirAttrName($modules);
2191		$return = array();
2192		if (isset($this->attributes['gidNumber'][0])) {
2193			$this->addPDFKeyValue($return, 'primaryGroup', _('Primary group'), $this->getGroupName($this->attributes['gidNumber'][0]));
2194		}
2195		$this->addPDFKeyValue($return, 'additionalGroups', _('Additional groups'), implode(", ", $additionalGroups));
2196		$this->addSimplePDFField($return, 'uid', $uidLabel);
2197		$this->addSimplePDFField($return, 'cn', _('Common name'));
2198		$this->addSimplePDFField($return, 'uidNumber', _('UID number'));
2199		$this->addSimplePDFField($return, 'gidNumber', _('GID number'));
2200		$this->addSimplePDFField($return, 'homeDirectory', _('Home directory'), $homeDirAttr);
2201		$this->addSimplePDFField($return, 'loginShell', _('Login shell'));
2202		$this->addSimplePDFField($return, 'gecos', _('Gecos'));
2203		if (self::areGroupOfNamesActive()) {
2204			$gons = array();
2205			for ($i = 0; $i < sizeof($this->gonList); $i++) {
2206				$gons[] = $this->gonList[$i];
2207			}
2208			usort($gons, 'compareDN');
2209			$gonCount = sizeof($gons);
2210			for ($i = 0; $i < $gonCount; $i++) {
2211				$gons[$i] = getAbstractDN($gons[$i]);
2212			}
2213			$this->addPDFKeyValue($return, 'gon', _('Groups of names'), $gons, "\n");
2214		}
2215		if (isset($this->clearTextPassword)) {
2216			$this->addPDFKeyValue($return, 'userPassword', _('Password'), $this->clearTextPassword);
2217		}
2218		else if (isset($this->attributes['INFO.userPasswordClearText'])) {
2219			$this->addPDFKeyValue($return, 'userPassword', _('Password'), $this->attributes['INFO.userPasswordClearText']);
2220		}
2221		return $return;
2222	}
2223
2224	/**
2225	 * {@inheritDoc}
2226	 * @see baseModule::get_configOptions()
2227	 */
2228	public function get_configOptions($scopes, $allScopes) {
2229		$typeManager = new TypeManager($_SESSION['conf_config']);
2230		$isWindows = array_key_exists('windowsUser', $allScopes);
2231		$return = array();
2232		$generatorOptions = array(
2233				_('Fixed range') => 'range',
2234				_('Samba ID pool') => 'sambaPool',
2235				_('Windows domain info') => 'windowsDomain',
2236				_('Magic number') => 'magicNumber'
2237		);
2238		$hasUserConfig = false;
2239		$hasHostConfig = false;
2240		foreach ($scopes as $typeId) {
2241			if (getScopeFromTypeId($typeId) === 'user') {
2242				$hasUserConfig = true;
2243			}
2244			elseif (getScopeFromTypeId($typeId) === 'host') {
2245				$hasHostConfig = true;
2246			}
2247		}
2248		if ($hasUserConfig) {
2249			// user options
2250			$configUserContainer = new htmlResponsiveRow();
2251			$configUserContainer->add(new htmlSubTitle(_("Users")), 12);
2252			foreach ($allScopes[get_class($this)] as $typeId) {
2253				if (!(getScopeFromTypeId($typeId) === 'user')) {
2254					continue;
2255				}
2256				if (sizeof($allScopes[get_class($this)]) > 1) {
2257					$title = new htmlDiv(null, new htmlOutputText($typeManager->getConfiguredType($typeId)->getAlias()));
2258					$title->setCSSClasses(array('bold', 'responsiveLabel'));
2259					$configUserContainer->add($title, 12, 6);
2260					$configUserContainer->add(new htmlOutputText('&nbsp;', false), 0, 6);
2261				}
2262				$uidGeneratorSelect = new htmlResponsiveSelect('posixAccount_' . $typeId . '_uidGeneratorUsers', $generatorOptions, array('range'), _('UID generator'), 'uidGenerator');
2263				$uidGeneratorSelect->setHasDescriptiveElements(true);
2264				$uidGeneratorSelect->setTableRowsToHide(array(
2265						'range' => array('posixAccount_' . $typeId . '_sambaIDPoolDNUsers', 'posixAccount_' . $typeId . '_windowsIDPoolDNUsers',
2266									'posixAccount_' . $typeId . '_magicNumberUser'),
2267						'sambaPool' => array('posixAccount_' . $typeId . '_minUID', 'posixAccount_' . $typeId . '_maxUID',
2268									'posixAccount_' . $typeId . '_windowsIDPoolDNUsers', 'posixAccount_' . $typeId . '_magicNumberUser'),
2269						'windowsDomain' => array('posixAccount_' . $typeId . '_minUID', 'posixAccount_' . $typeId . '_maxUID',
2270									'posixAccount_' . $typeId . '_sambaIDPoolDNUsers', 'posixAccount_' . $typeId . '_magicNumberUser'),
2271						'magicNumber' => array('posixAccount_' . $typeId . '_minUID', 'posixAccount_' . $typeId . '_maxUID',
2272									'posixAccount_' . $typeId . '_windowsIDPoolDNUsers', 'posixAccount_' . $typeId . '_sambaIDPoolDNUsers')
2273				));
2274				$uidGeneratorSelect->setTableRowsToShow(array(
2275						'range' => array('posixAccount_' . $typeId . '_minUID', 'posixAccount_' . $typeId . '_maxUID'),
2276						'sambaPool' => array('posixAccount_' . $typeId . '_sambaIDPoolDNUsers'),
2277						'windowsDomain' => array('posixAccount_' . $typeId . '_windowsIDPoolDNUsers'),
2278						'magicNumber' => array('posixAccount_' . $typeId . '_magicNumberUser')
2279				));
2280				$configUserContainer->add($uidGeneratorSelect, 12);
2281				$uidUsersGeneratorDN = new htmlResponsiveInputField(_('Samba ID pool DN'), 'posixAccount_' . $typeId . '_sambaIDPoolDNUsers', null, 'sambaIDPoolDN');
2282				$uidUsersGeneratorDN->setRequired(true);
2283				$configUserContainer->add($uidUsersGeneratorDN, 12);
2284				$uidUsersGeneratorWinDN = new htmlResponsiveInputField(_('Windows domain info DN'), 'posixAccount_' . $typeId . '_windowsIDPoolDNUsers', null, 'windowsIDPoolDN');
2285				$uidUsersGeneratorWinDN->setRequired(true);
2286				$configUserContainer->add($uidUsersGeneratorWinDN, 12);
2287				$minUid = new htmlResponsiveInputField(_('Minimum UID number'), 'posixAccount_' . $typeId . '_minUID', null, 'minMaxUser');
2288				$minUid->setRequired(true);
2289				$configUserContainer->add($minUid, 12);
2290				$maxUid = new htmlResponsiveInputField(_('Maximum UID number'), 'posixAccount_' . $typeId . '_maxUID', null, 'minMaxUser');
2291				$maxUid->setRequired(true);
2292				$configUserContainer->add($maxUid, 12);
2293				$magicNumberUser = new htmlResponsiveInputField(_('Magic number'), 'posixAccount_' . $typeId . '_magicNumberUser', null, 'magicNumber');
2294				$magicNumberUser->setRequired(true);
2295				$configUserContainer->add($magicNumberUser, 12);
2296				$configUserContainer->add(new htmlResponsiveInputField(_('Suffix for UID/user name check'), 'posixAccount_' . $typeId . '_uidCheckSuffixUser', '', 'uidCheckSuffix'), 12);
2297				$configUserContainer->add(new htmlResponsiveInputField(_('User name suggestion'), 'posixAccount_' . $typeId . '_userNameSuggestion', '@givenname@%sn%', 'userNameSuggestion'), 12);
2298				$configUserContainer->addVerticalSpacer('2rem');
2299				$hiddenOptionsContainerHead = new htmlGroup();
2300				$hiddenOptionsContainerHead->addElement(new htmlOutputText(_('Hidden options')));
2301				$hiddenOptionsContainerHead->addElement(new htmlHelpLink('hiddenOptions'));
2302				$configUserContainer->add($hiddenOptionsContainerHead, 12);
2303				$configUserContainer->addVerticalSpacer('0.5rem');
2304				$configUserContainer->add(new htmlResponsiveInputCheckbox('posixAccount_' . $typeId . '_hidegecos', false, _('Gecos'), null, true), 12, 4, 4);
2305				$configUserContainer->add(new htmlResponsiveInputCheckbox('posixAccount_' . $typeId . '_hidepassword', false, _('Password'), null, true), 12, 4, 4);
2306				$confActiveGONModules = array_merge($_SESSION['conf_config']->get_AccountModules('group'), $_SESSION['conf_config']->get_AccountModules('gon'));
2307				if (in_array('groupOfNames', $confActiveGONModules) || in_array('groupOfMembers', $confActiveGONModules) || in_array('groupOfUniqueNames', $confActiveGONModules)) {
2308					$configUserContainer->add(new htmlResponsiveInputCheckbox('posixAccount_' . $typeId . '_hidegon', false, _('Groups of names'), null, true), 12, 4, 4);
2309					$configUserContainer->add(new htmlResponsiveInputCheckbox('posixAccount_' . $typeId . '_hideCreateGroup', false, _('Create group with same name'), null, true), 12, 4, 4);
2310					$configUserContainer->add(new htmlResponsiveInputCheckbox('posixAccount_' . $typeId . '_hideposixGroups', false, _('Unix groups'), null, true), 12, 4, 4);
2311					$syncGroupsCheckbox = new htmlResponsiveInputCheckbox('posixAccount_' . $typeId . '_syncGroups', false, _('Sync groups'), null, true);
2312					$syncGroupsCheckbox->setTableRowsToHide(array('posixAccount_' . $typeId . '_syncGroupsExclusions'));
2313					$configUserContainer->add($syncGroupsCheckbox, 12, 4, 4);
2314					$configUserContainer->add(new htmlResponsiveInputTextarea('posixAccount_' . $typeId . '_syncGroupsExclusions', '', 20, 4, _('Exclude from group sync'), 'excludeFromGroupSync'), 12);
2315				}
2316			}
2317			$return[] = $configUserContainer;
2318		}
2319		// host options
2320		if ($hasHostConfig) {
2321			$configHostContainer = new htmlResponsiveRow();
2322			$configHostContainer->add(new htmlSubTitle(_("Hosts")), 12);
2323			foreach ($allScopes[get_class($this)] as $typeId) {
2324				if (!(getScopeFromTypeId($typeId) === 'host')) {
2325					continue;
2326				}
2327				if (sizeof($allScopes[get_class($this)]) > 1) {
2328					$title = new htmlDiv(null, new htmlOutputText($typeManager->getConfiguredType($typeId)->getAlias()));
2329					$title->setCSSClasses(array('bold', 'responsiveLabel'));
2330					$configHostContainer->add($title, 12, 6);
2331					$configHostContainer->add(new htmlOutputText('&nbsp;', false), 0, 6);
2332				}
2333				$uidHostGeneratorSelect = new htmlResponsiveSelect('posixAccount_' . $typeId . '_uidGeneratorHosts', $generatorOptions, array('range'), _('UID generator'), 'uidGenerator');
2334				$uidHostGeneratorSelect->setHasDescriptiveElements(true);
2335				$uidHostGeneratorSelect->setTableRowsToHide(array(
2336						'range' => array('posixAccount_' . $typeId . '_sambaIDPoolDNHosts', 'posixAccount_' . $typeId . '_windowsIDPoolDNHosts',
2337										'posixAccount_' . $typeId . '_magicNumberHost'),
2338						'sambaPool' => array('posixAccount_' . $typeId . '_minMachine', 'posixAccount_' . $typeId . '_maxMachine',
2339										'posixAccount_' . $typeId . '_windowsIDPoolDNHosts', 'posixAccount_' . $typeId . '_magicNumberHost'),
2340						'windowsDomain' => array('posixAccount_' . $typeId . '_minMachine', 'posixAccount_' . $typeId . '_maxMachine',
2341										'posixAccount_' . $typeId . '_sambaIDPoolDNHosts', 'posixAccount_' . $typeId . '_magicNumberHost'),
2342						'magicNumber' => array('posixAccount_' . $typeId . '_minMachine', 'posixAccount_' . $typeId . '_maxMachine',
2343										'posixAccount_' . $typeId . '_windowsIDPoolDNHosts', 'posixAccount_' . $typeId . '_sambaIDPoolDNHosts')
2344				));
2345				$uidHostGeneratorSelect->setTableRowsToShow(array(
2346						'range' => array('posixAccount_' . $typeId . '_minMachine', 'posixAccount_' . $typeId . '_maxMachine'),
2347						'sambaPool' => array('posixAccount_' . $typeId . '_sambaIDPoolDNHosts'),
2348						'windowsDomain' => array('posixAccount_' . $typeId . '_windowsIDPoolDNHosts'),
2349						'magicNumber' => array('posixAccount_' . $typeId . '_magicNumberHost')
2350				));
2351				$configHostContainer->add($uidHostGeneratorSelect, 12);
2352				$uidHostsGeneratorDN = new htmlResponsiveInputField(_('Samba ID pool DN'), 'posixAccount_' . $typeId . '_sambaIDPoolDNHosts', null, 'sambaIDPoolDN');
2353				$uidHostsGeneratorDN->setRequired(true);
2354				$configHostContainer->add($uidHostsGeneratorDN, 12);
2355				$uidHostsGeneratorWinDN = new htmlResponsiveInputField(_('Windows domain info DN'), 'posixAccount_' . $typeId . '_windowsIDPoolDNHosts', null, 'windowsIDPoolDN');
2356				$uidHostsGeneratorWinDN->setRequired(true);
2357				$configHostContainer->add($uidHostsGeneratorWinDN, 12);
2358				$minUid = new htmlResponsiveInputField(_('Minimum UID number'), 'posixAccount_' . $typeId . '_minMachine', null, 'minMaxHost');
2359				$minUid->setRequired(true);
2360				$configHostContainer->add($minUid, 12);
2361				$maxUid = new htmlResponsiveInputField(_('Maximum UID number'), 'posixAccount_' . $typeId . '_maxMachine', null, 'minMaxHost');
2362				$maxUid->setRequired(true);
2363				$configHostContainer->add($maxUid, 12);
2364				$magicNumberHost = new htmlResponsiveInputField(_('Magic number'), 'posixAccount_' . $typeId . '_magicNumberHost', null, 'magicNumber');
2365				$magicNumberHost->setRequired(true);
2366				$configHostContainer->add($magicNumberHost, 12);
2367				$configHostContainer->add(new htmlResponsiveInputField(_('Suffix for UID/user name check'), 'posixAccount_' . $typeId . '_uidCheckSuffixHost', '', 'uidCheckSuffix'), 12);
2368				$hiddenOptionsContainerHead = new htmlGroup();
2369				$hiddenOptionsContainerHead->addElement(new htmlOutputText(_('Hidden options')));
2370				$hiddenOptionsContainerHead->addElement(new htmlHelpLink('hiddenOptions'));
2371				$configHostContainer->addLabel($hiddenOptionsContainerHead, 12);
2372				$configHostContainer->addField(new htmlOutputText(''));
2373				$configHostContainer->addVerticalSpacer('0.5rem');
2374				$configHostContainer->add(new htmlResponsiveInputCheckbox('posixAccount_' . $typeId . '_hidegecos', false, _('Gecos'), null, false), 12);
2375				$configHostContainer->addVerticalSpacer('2rem');
2376			}
2377			$return[] = $configHostContainer;
2378		}
2379		// common options
2380		$configOptionsContainer = new htmlResponsiveRow();
2381		$configOptionsContainer->add(new htmlSubTitle(_('Options')), 12);
2382		$configOptionsContainer->add(new htmlResponsiveSelect('posixAccount_pwdHash', getSupportedHashTypes(),
2383				array('CRYPT-SHA512'), _("Password hash type"), 'pwdHash'), 12);
2384		$configOptionsContainer->add(new htmlResponsiveInputTextarea('posixAccount_shells', implode("\r\n", $this->getShells()), 30, 4, _('Login shells'), 'loginShells'), 12);
2385		$configOptionsContainer->add(new htmlResponsiveInputCheckbox('posixAccount_primaryGroupAsSecondary', false, _('Set primary group as memberUid'), 'primaryGroupAsSecondary'), 12);
2386		if ($isWindows) {
2387			$configOptionsContainer->add(new htmlResponsiveInputCheckbox('posixAccount_noObjectClass', false, _('Do not add object class'), 'noObjectClass'), 12);
2388		}
2389		$return[] = $configOptionsContainer;
2390
2391		return $return;
2392	}
2393
2394	/**
2395	 * {@inheritDoc}
2396	 * @see baseModule::check_configOptions()
2397	 */
2398	public function check_configOptions($typeIds, &$options) {
2399		$return = array();
2400		$scopes = array();
2401		$ranges = array();
2402		foreach ($typeIds as $typeId) {
2403			$scopes[] = getScopeFromTypeId($typeId);
2404			// user settings
2405			if (getScopeFromTypeId($typeId) === 'user') {
2406				if ($options['posixAccount_' . $typeId . '_uidGeneratorUsers'][0] == 'range') {
2407					// min/maxUID are required, check if they are numeric
2408					if (!isset($options['posixAccount_' . $typeId . '_minUID'][0]) || !preg_match('/^[0-9]+$/', $options['posixAccount_' . $typeId . '_minUID'][0])) {
2409						$return[] = $this->messages['minUID'][0];
2410					}
2411					if (!isset($options['posixAccount_' . $typeId . '_maxUID'][0]) || !preg_match('/^[0-9]+$/', $options['posixAccount_' . $typeId . '_maxUID'][0])) {
2412						$return[] = $this->messages['maxUID'][0];
2413					}
2414					// minUID < maxUID
2415					if (isset($options['posixAccount_' . $typeId . '_minUID'][0]) && isset($options['posixAccount_' . $typeId . '_maxUID'][0])) {
2416						if ($options['posixAccount_' . $typeId . '_minUID'][0] >= $options['posixAccount_' . $typeId . '_maxUID'][0]) {
2417							$return[] = $this->messages['cmp_UID'][0];
2418						}
2419					}
2420					$ranges[] = array($options['posixAccount_' . $typeId . '_minUID'][0], $options['posixAccount_' . $typeId . '_maxUID'][0]);
2421				}
2422				elseif ($options['posixAccount_' . $typeId . '_uidGeneratorUsers'][0] == 'sambaPool') {
2423					if (!isset($options['posixAccount_' . $typeId . '_sambaIDPoolDNUsers'][0]) || !get_preg($options['posixAccount_' . $typeId . '_sambaIDPoolDNUsers'][0], 'dn')) {
2424						$return[] = $this->messages['sambaIDPoolDN'][0];
2425					}
2426				}
2427				elseif ($options['posixAccount_' . $typeId . '_uidGeneratorUsers'][0] == 'windowsDomain') {
2428					if (!isset($options['posixAccount_' . $typeId . '_windowsIDPoolDNUsers'][0]) || !get_preg($options['posixAccount_' . $typeId . '_windowsIDPoolDNUsers'][0], 'dn')) {
2429						$return[] = $this->messages['windowsIDPoolDN'][0];
2430					}
2431				}
2432			}
2433			// host settings
2434			if (getScopeFromTypeId($typeId) === 'host') {
2435				if ($options['posixAccount_' . $typeId . '_uidGeneratorHosts'][0] == 'range') {
2436					// min/maxUID are required, check if they are numeric
2437					if (!isset($options['posixAccount_' . $typeId . '_minMachine'][0]) || !preg_match('/^[0-9]+$/', $options['posixAccount_' . $typeId . '_minMachine'][0])) {
2438						$return[] = $this->messages['minMachine'][0];
2439					}
2440					if (!isset($options['posixAccount_' . $typeId . '_maxMachine'][0]) || !preg_match('/^[0-9]+$/', $options['posixAccount_' . $typeId . '_maxMachine'][0])) {
2441						$return[] = $this->messages['maxMachine'][0];
2442					}
2443					// minUID < maxUID
2444					if (isset($options['posixAccount_' . $typeId . '_minMachine'][0]) && isset($options['posixAccount_' . $typeId . '_maxMachine'][0])) {
2445						if ($options['posixAccount_' . $typeId . '_minMachine'][0] >= $options['posixAccount_' . $typeId . '_maxMachine'][0]) {
2446							$return[] = $this->messages['cmp_Machine'][0];
2447						}
2448					}
2449					$ranges[] = array($options['posixAccount_' . $typeId . '_minMachine'][0], $options['posixAccount_' . $typeId . '_maxMachine'][0]);
2450				}
2451				elseif ($options['posixAccount_' . $typeId . '_uidGeneratorHosts'][0] == 'sambaPool') {
2452					if (!isset($options['posixAccount_' . $typeId . '_sambaIDPoolDNHosts'][0]) || !get_preg($options['posixAccount_' . $typeId . '_sambaIDPoolDNHosts'][0], 'dn')) {
2453						$return[] = $this->messages['sambaIDPoolDN'][0];
2454					}
2455				}
2456				elseif ($options['posixAccount_' . $typeId . '_uidGeneratorHosts'][0] == 'windowsDomain') {
2457					if (!isset($options['posixAccount_' . $typeId . '_windowsIDPoolDNHosts'][0]) || !get_preg($options['posixAccount_' . $typeId . '_windowsIDPoolDNHosts'][0], 'dn')) {
2458						$return[] = $this->messages['windowsIDPoolDN'][0];
2459					}
2460				}
2461			}
2462		}
2463		// check if user and host ranges overlap
2464		foreach ($ranges as $range) {
2465			foreach ($ranges as $rangeToCompare) {
2466				// check if minimum is inside other range
2467				if (($rangeToCompare[0] > $range[0]) && ($rangeToCompare[0] < $range[1])) {
2468					$return[] = $this->messages['cmp_both'][0];
2469					break 2;
2470				}
2471				// check if maximum is inside other range
2472				if (($rangeToCompare[1] > $range[0]) && ($rangeToCompare[1] < $range[1])) {
2473					$return[] = $this->messages['cmp_both'][0];
2474					break 2;
2475				}
2476			}
2477		}
2478		return $return;
2479	}
2480
2481	/**
2482	 * {@inheritDoc}
2483	 * @see baseModule::getManagedAttributes()
2484	 */
2485	function get_uploadColumns($selectedModules, &$type) {
2486		$typeId = $type->getId();
2487		$return = parent::get_uploadColumns($selectedModules, $type);
2488		if ($this->isPasswordManaged($typeId)) {
2489			$return[] = array(
2490				'name' => 'posixAccount_password',
2491				'description' => _('Password'),
2492				'help' => 'userPassword',
2493				'example' => _('secret')
2494			);
2495			$return[] = array(
2496				'name' => 'posixAccount_passwordDisabled',
2497				'description' => _('Lock password'),
2498				'help' => 'userPassword_lock',
2499				'example' => 'false',
2500				'values' => 'true, false',
2501				'default' => 'false'
2502			);
2503		}
2504		if (($this->get_scope() == 'user') && $this->manageCn($selectedModules)) {
2505			array_unshift($return, array(
2506				'name' => 'posixAccount_cn',
2507				'description' => _('Common name'),
2508				'help' => 'cn',
2509				'example' => _('Steve Miller')
2510			));
2511		}
2512		if (($this->get_scope() == 'user') && !$this->isBooleanConfigOptionSet('posixAccount_' . $typeId . '_hidegecos')) {
2513			$return[] = array(
2514				'name' => 'posixAccount_gecos',
2515				'description' => _('Gecos'),
2516				'help' => 'gecos',
2517				'example' => _('Steve Miller,Room 2.14,123-123-1234,123-123-1234')
2518			);
2519		}
2520		if (($this->get_scope() == 'host') && !$this->isBooleanConfigOptionSet('posixAccount_' . $typeId . '_hidegecos')) {
2521			$return[] = array(
2522				'name' => 'posixAccount_gecos',
2523				'description' => _('Gecos'),
2524				'help' => 'gecos',
2525				'example' => _('pc01,Room 2.34')
2526			);
2527		}
2528		return $return;
2529	}
2530
2531	/**
2532	 * {@inheritDoc}
2533	 * @see baseModule::build_uploadAccounts()
2534	 */
2535	function build_uploadAccounts($rawAccounts, $ids, &$partialAccounts, $selectedModules, &$type) {
2536		$errors = array();
2537		$typeId = $type->getId();
2538		$pwdAttrName = $this->getPasswordAttrName($selectedModules);
2539		$homedirAttrName = $this->getHomedirAttrName($selectedModules);
2540		$needAutoUID = array();
2541		// get list of existing users
2542		$existingUsers = $this->getUserNames($typeId);
2543		// get list of existing groups
2544		$groupList = $this->findGroups($selectedModules);
2545		$groupMap = array();
2546		for ($i = 0; $i < sizeof($groupList); $i++) {
2547			$groupMap[$groupList[$i][1]] = $groupList[$i][0];
2548		}
2549		$existingGroups = array_keys($groupMap);
2550		// get list of existing group of names
2551		if (self::areGroupOfNamesActive()) {
2552			$gons = $this->findGroupOfNames();
2553			$gonList = array();
2554			foreach ($gons as $attr) {
2555				$gonList[] = $attr['cn'][0];
2556			}
2557		}
2558		// check input
2559		foreach ($rawAccounts as $i => $rawAccount) {
2560			if (!$this->skipObjectClass() && !in_array("posixAccount", $partialAccounts[$i]['objectClass'])) {
2561				$partialAccounts[$i]['objectClass'][] = "posixAccount";
2562			}
2563			// UID
2564			if ($rawAccount[$ids['posixAccount_uid']] == "") {
2565				// autoUID
2566				$needAutoUID[] = $i;
2567			}
2568			elseif (get_preg($rawAccount[$ids['posixAccount_uid']], 'digit')) {
2569				if (($this->get_scope() == 'user') && ($this->moduleSettings['posixAccount_' . $typeId . '_uidGeneratorUsers'][0] == 'range')) {
2570					if (($rawAccount[$ids['posixAccount_uid']] > $this->moduleSettings['posixAccount_' . $typeId . '_minUID'][0]) &&
2571						($rawAccount[$ids['posixAccount_uid']] < $this->moduleSettings['posixAccount_' . $typeId . '_maxUID'][0])) {
2572						$partialAccounts[$i]['uidNumber'] = trim($rawAccount[$ids['posixAccount_uid']]);
2573					}
2574					else {
2575						$errMsg = $this->messages['uidNumber'][4];
2576						array_push($errMsg, array($i));
2577						$errors[] = $errMsg;
2578					}
2579				}
2580				elseif (($this->get_scope() == 'host') && ($this->moduleSettings['posixAccount_' . $typeId . '_uidGeneratorHosts'][0] == 'range')) {
2581					if (($rawAccount[$ids['posixAccount_uid']] > $this->moduleSettings['posixAccount_' . $typeId . '_minMachine'][0]) &&
2582						($rawAccount[$ids['posixAccount_uid']] < $this->moduleSettings['posixAccount_' . $typeId . '_maxMachine'][0])) {
2583						$partialAccounts[$i]['uidNumber'] = trim($rawAccount[$ids['posixAccount_uid']]);
2584					}
2585					else {
2586						$errMsg = $this->messages['uidNumber'][4];
2587						array_push($errMsg, array($i));
2588						$errors[] = $errMsg;
2589					}
2590				}
2591			}
2592			else {
2593				$errMsg = $this->messages['uidNumber'][4];
2594				array_push($errMsg, array($i));
2595				$errors[] = $errMsg;
2596			}
2597			// GID number
2598			if (get_preg($rawAccount[$ids['posixAccount_group']], 'digit')) {
2599				$partialAccounts[$i]['gidNumber'] = $rawAccount[$ids['posixAccount_group']];
2600			}
2601			if (get_preg($rawAccount[$ids['posixAccount_group']], 'groupname')) {
2602				$groupName = $rawAccount[$ids['posixAccount_group']];
2603				$gid = nuLL;
2604				if (isset($groupMap[$groupName])) {
2605					$gid = $groupMap[$groupName];
2606				}
2607				if (is_numeric($gid)) {
2608					$partialAccounts[$i]['gidNumber'] = $gid;
2609				}
2610				else {
2611					$errMsg = $this->messages['gidNumber'][0];
2612					array_push($errMsg, array($i));
2613					$errors[] = $errMsg;
2614				}
2615			}
2616			else {
2617				$errMsg = $this->messages['gidNumber'][1];
2618				array_push($errMsg, array($i));
2619				$errors[] = $errMsg;
2620			}
2621			// GECOS
2622			if (!$this->isBooleanConfigOptionSet('posixAccount_' . $typeId . '_hidegecos')) {
2623				if (!empty($rawAccount[$ids['posixAccount_gecos']])) {
2624					if (get_preg($rawAccount[$ids['posixAccount_gecos']], 'gecos')) {
2625						$partialAccounts[$i]['gecos'] = $this->checkASCII($rawAccount[$ids['posixAccount_gecos']]);
2626					}
2627					else {
2628						$errMsg = $this->messages['gecos'][0];
2629						array_push($errMsg, array($i));
2630						$errors[] = $errMsg;
2631					}
2632				}
2633				else {
2634					$gecos = "";
2635					if (($rawAccount[$ids['inetOrgPerson_firstName']] != "") && ($rawAccount[$ids['inetOrgPerson_lastName']] != "")) {
2636						$gecos = $rawAccount[$ids['inetOrgPerson_firstName']] . " " . $rawAccount[$ids['inetOrgPerson_lastName']];
2637						if (!empty($rawAccount[$ids['inetOrgPerson_telephone']])) {
2638							$gecos = $gecos . ",," . $rawAccount[$ids['inetOrgPerson_telephone']];  // double "," because room is unknown
2639							if (!empty($rawAccount[$ids['inetOrgPerson_fax']])) {
2640								$gecos = $gecos . "," . $rawAccount[$ids['inetOrgPerson_fax']];
2641							}
2642						}
2643					}
2644					if (!empty($gecos)) {
2645						$partialAccounts[$i]['gecos'] = $this->checkASCII($gecos);
2646					}
2647				}
2648			}
2649			// user specific attributes
2650			if ($this->get_scope() == 'user') {
2651				// additional groups
2652				if ($rawAccount[$ids['posixAccount_additionalGroups']] != "") {
2653					$groups = explode(",", $rawAccount[$ids['posixAccount_additionalGroups']]);
2654					for ($g = 0; $g < sizeof($groups); $g++) {
2655						if (!in_array($groups[$g], $existingGroups)) {
2656							$errors[] = array('ERROR', _('Unable to find group in LDAP.'), $groups[$g]);
2657						}
2658					}
2659				}
2660				// group of names
2661				if (self::areGroupOfNamesActive() && ($rawAccount[$ids['posixAccount_gon']] != "")) {
2662					$groups = explode(",", $rawAccount[$ids['posixAccount_gon']]);
2663					for ($g = 0; $g < sizeof($groups); $g++) {
2664						if (!in_array($groups[$g], $gonList)) {
2665							$errors[] = array('ERROR', _('Unable to find group in LDAP.'), $groups[$g]);
2666						}
2667					}
2668				}
2669				// user name
2670				if (array_key_exists($rawAccount[$ids['posixAccount_userName']], $existingUsers)) {
2671					$userName = $rawAccount[$ids['posixAccount_userName']];
2672					while (array_key_exists($userName, $existingUsers)) {
2673						$userName = $this->getNextUserName($userName, $selectedModules);
2674					}
2675					$errMsg = $this->messages['uid'][9];
2676					array_push($errMsg, array($i, $userName, $rawAccount[$ids['posixAccount_userName']],
2677						htmlspecialchars($existingUsers[$rawAccount[$ids['posixAccount_userName']]])));
2678					$errors[] = $errMsg;
2679				}
2680				if (get_preg($rawAccount[$ids['posixAccount_userName']], 'username')) {
2681					$partialAccounts[$i]['uid'] = $rawAccount[$ids['posixAccount_userName']];
2682				}
2683				else {
2684					$errMsg = $this->messages['uid'][7];
2685					array_push($errMsg, array($i));
2686					$errors[] = $errMsg;
2687				}
2688				// home directory
2689				if ($rawAccount[$ids['posixAccount_homedir']] == "") {
2690					$partialAccounts[$i][$homedirAttrName] = '/home/' . $partialAccounts[$i]['uid'];
2691				}
2692				elseif (get_preg($rawAccount[$ids['posixAccount_homedir']], 'homeDirectory')) {
2693					$partialAccounts[$i][$homedirAttrName] = $rawAccount[$ids['posixAccount_homedir']];
2694				}
2695				else {
2696					$errMsg = $this->messages['homeDirectory'][2];
2697					array_push($errMsg, array($i));
2698					$errors[] = $errMsg;
2699				}
2700				// login shell
2701				if ($rawAccount[$ids['posixAccount_shell']] == "") {
2702					$partialAccounts[$i]['loginShell'] = '/bin/bash';
2703				}
2704				elseif (in_array($rawAccount[$ids['posixAccount_shell']], $this->getShells())) {
2705					$partialAccounts[$i]['loginShell'] = $rawAccount[$ids['posixAccount_shell']];
2706				}
2707				else {
2708					$errMsg = $this->messages['shell'][0];
2709					array_push($errMsg, array($i));
2710					$errors[] = $errMsg;
2711				}
2712				if ($this->isPasswordManaged($typeId)) {
2713					$pwd_enabled = true;
2714					// password enabled/disabled
2715					if ($rawAccount[$ids['posixAccount_passwordDisabled']] == "") {
2716						$pwd_enabled = true;
2717					}
2718					elseif (in_array($rawAccount[$ids['posixAccount_passwordDisabled']], array('true', 'false'))) {
2719						if ($rawAccount[$ids['posixAccount_passwordDisabled']] == 'true') {
2720							$pwd_enabled = false;
2721						}
2722						else {
2723							$pwd_enabled = true;
2724						}
2725					}
2726					else {
2727						$errMsg = $this->messages['passwordDisabled'][0];
2728						array_push($errMsg, array($i));
2729						$errors[] = $errMsg;
2730					}
2731					// password
2732					// delay exop passwords
2733					if (!empty($this->moduleSettings['posixAccount_pwdHash'][0]) && ($this->moduleSettings['posixAccount_pwdHash'][0] === 'LDAP_EXOP')) {
2734						// changed in post action
2735					}
2736					// set SASL passwords
2737					elseif (!empty($this->moduleSettings['posixAccount_pwdHash'][0]) && ($this->moduleSettings['posixAccount_pwdHash'][0] === 'SASL')) {
2738						$partialAccounts[$i][$pwdAttrName] = '{SASL}' . $partialAccounts[$i]['uid'];
2739					}
2740					// set K5KEY password
2741					elseif (!empty($this->moduleSettings['posixAccount_pwdHash'][0]) && ($this->moduleSettings['posixAccount_pwdHash'][0] === 'K5KEY')) {
2742						$partialAccounts[$i][$pwdAttrName] = pwd_hash('x', true, $this->moduleSettings['posixAccount_pwdHash'][0]);
2743					}
2744					// set normal password
2745					else {
2746						if (($rawAccount[$ids['posixAccount_password']] != "") && (get_preg($rawAccount[$ids['posixAccount_password']], 'password'))) {
2747							$partialAccounts[$i][$pwdAttrName] = pwd_hash($rawAccount[$ids['posixAccount_password']], $pwd_enabled, $this->moduleSettings['posixAccount_pwdHash'][0]);
2748							$partialAccounts[$i]['INFO.userPasswordClearText'] = $rawAccount[$ids['posixAccount_password']]; // for custom scripts etc.
2749						}
2750						elseif ($rawAccount[$ids['posixAccount_password']] != "") {
2751							$errMsg = $this->messages['userPassword'][4];
2752							$errMsg[2] = str_replace('%', '%%', $errMsg[2]); // double "%" because of later sprintf
2753							array_push($errMsg, array($i));
2754							$errors[] = $errMsg;
2755						}
2756					}
2757				}
2758				// cn
2759				if ($this->manageCn($selectedModules)) {
2760					if ($rawAccount[$ids['posixAccount_cn']] != "") {
2761						if (get_preg($rawAccount[$ids['posixAccount_cn']], 'cn')) {
2762							$partialAccounts[$i]['cn'] = $rawAccount[$ids['posixAccount_cn']];
2763						}
2764						else {
2765							$errMsg = $this->messages['cn'][1];
2766							array_push($errMsg, array($i));
2767							$errors[] = $errMsg;
2768						}
2769					}
2770					else {
2771						if ($partialAccounts[$i]['givenName']) {
2772							$partialAccounts[$i]['cn'] = $partialAccounts[$i]['givenName'] . " " . $partialAccounts[$i]['sn'];
2773						}
2774						elseif ($partialAccounts[$i]['sn']) {
2775							$partialAccounts[$i]['cn'] = $partialAccounts[$i]['sn'];
2776						}
2777						else {
2778							$partialAccounts[$i]['cn'] = $partialAccounts[$i]['uid'];
2779						}
2780					}
2781				}
2782			}
2783			// host specific attributes
2784			elseif ($this->get_scope() == 'host') {
2785				// host name
2786				if (array_key_exists($rawAccount[$ids['posixAccount_hostName']], $existingUsers)) {
2787					$userName = $rawAccount[$ids['posixAccount_hostName']];
2788					while (array_key_exists($userName, $existingUsers)) {
2789						$userName = $this->getNextUserName($userName, $selectedModules);
2790					}
2791					$errMsg = $this->messages['uid'][10];
2792					array_push($errMsg, array($i, $userName, $rawAccount[$ids['posixAccount_hostName']],
2793						htmlspecialchars($existingUsers[$rawAccount[$ids['posixAccount_hostName']]])));
2794					$errors[] = $errMsg;
2795				}
2796				if (get_preg($rawAccount[$ids['posixAccount_hostName']], 'hostname')) {
2797					$partialAccounts[$i]['uid'] = $rawAccount[$ids['posixAccount_hostName']];
2798					$partialAccounts[$i]['cn'] = $rawAccount[$ids['posixAccount_hostName']];
2799				}
2800				else {
2801					$errMsg = $this->messages['uid'][8];
2802					array_push($errMsg, array($i));
2803					$errors[] = $errMsg;
2804				}
2805				// description
2806				if (isset($ids['posixAccount_description']) && isset($rawAccount[$ids['posixAccount_description']]) && ($rawAccount[$ids['posixAccount_description']] != '')) {
2807					$partialAccounts[$i]['description'] = $rawAccount[$ids['posixAccount_description']];
2808				}
2809				else {
2810					$partialAccounts[$i]['description'] = $rawAccount[$ids['posixAccount_hostName']];
2811				}
2812				$partialAccounts[$i][$homedirAttrName] = '/dev/null';
2813				$partialAccounts[$i]['loginShell'] = '/bin/false';
2814			}
2815		}
2816		// fill in autoUIDs
2817		if (sizeof($needAutoUID) > 0) {
2818			$errorsTemp = array();
2819			$uids = $this->getNextUIDs(sizeof($needAutoUID), $errorsTemp, $typeId);
2820			if (is_array($uids)) {
2821				foreach ($needAutoUID as $i => $index) {
2822					$partialAccounts[$index]['uidNumber'] = $uids[$i];
2823				}
2824			}
2825			else {
2826				$errors[] = $this->messages['uidNumber'][2];
2827			}
2828		}
2829		return $errors;
2830	}
2831
2832	/**
2833	 * {@inheritDoc}
2834	 * @see baseModule::doUploadPostActions()
2835	 */
2836	function doUploadPostActions(&$data, $ids, $failed, &$temp, &$accounts, $selectedModules, $type) {
2837		if (!checkIfWriteAccessIsAllowed($this->get_scope())) {
2838			die();
2839		}
2840		$homeDirAttr = $this->getHomedirAttrName($selectedModules);
2841		if ($this->get_scope() != 'user') {
2842			return array(
2843				'status' => 'finished',
2844				'progress' => 100,
2845				'errors' => array()
2846			);
2847		}
2848		// on first call generate list of ldap operations
2849		if (!isset($temp['counter'])) {
2850			$temp['groups'] = array();
2851			$temp['dn_gon'] = array();
2852			$temp['createHomes'] = array();
2853			$temp['exop'] = array();
2854			$temp['counter'] = 0;
2855			$col = $ids['posixAccount_additionalGroups'];
2856			$col_home = $ids['posixAccount_createHomeDir'];
2857			// get list of existing groups
2858			$groupList = $this->findGroups($selectedModules);
2859			$groupMap = array();
2860			for ($i = 0; $i < sizeof($groupList); $i++) {
2861				$groupMap[$groupList[$i][0]] = $groupList[$i][1];
2862			}
2863			// get list of existing group of names
2864			if (self::areGroupOfNamesActive()) {
2865				$gonList = $this->findGroupOfNames();
2866				$gonMap = array();
2867				foreach ($gonList as $dn => $attr) {
2868					$gonMap[$attr['cn'][0]] = $dn;
2869				}
2870			}
2871			foreach ($data as $i => $dataRow) {
2872				if (in_array($i, $failed)) continue; // ignore failed accounts
2873				if ($dataRow[$col] != "") {
2874					$groups = explode(",", $dataRow[$col]);
2875					if (isset($this->moduleSettings['posixAccount_primaryGroupAsSecondary'][0])
2876						&& ($this->moduleSettings['posixAccount_primaryGroupAsSecondary'][0] == 'true')) {
2877						if (get_preg($dataRow[$ids['posixAccount_group']], 'digit')) {
2878							if (!in_array($groupMap[$dataRow[$ids['posixAccount_group']]], $groups)) {
2879								$groups[] = $groupMap[$dataRow[$ids['posixAccount_group']]];
2880							}
2881						}
2882						else {
2883							if (!in_array($groupMap[$dataRow[$ids['posixAccount_group']]], $groups)) {
2884								$groups[] = $dataRow[$ids['posixAccount_group']];
2885							}
2886						}
2887					}
2888					for ($g = 0; $g < sizeof($groups); $g++) {
2889						if (!in_array($groups[$g], $temp['groups'])) $temp['groups'][] = $groups[$g];
2890						$temp['members'][$groups[$g]][] = $dataRow[$ids['posixAccount_userName']];
2891					}
2892				}
2893				if (isset($ids['posixAccount_gon']) && ($dataRow[$ids['posixAccount_gon']] != "")) {
2894					$gons = explode(",", $dataRow[$ids['posixAccount_gon']]);
2895					$memberAttr = 'member';
2896					for ($g = 0; $g < sizeof($gons); $g++) {
2897						if (in_array_ignore_case('groupOfUniqueNames', $gonList[$gonMap[$gons[$g]]]['objectclass'])) {
2898							$memberAttr = 'uniqueMember';
2899						}
2900						$temp['dn_gon'][$gonMap[$gons[$g]]][$memberAttr][] = $accounts[$i]['dn'];
2901					}
2902				}
2903				if (!empty($dataRow[$col_home])) {
2904					$temp['createHomes'][] = $i;
2905				}
2906				if (!empty($this->moduleSettings['posixAccount_pwdHash'][0]) && ($this->moduleSettings['posixAccount_pwdHash'][0] === 'LDAP_EXOP')) {
2907					if (isset($ids['posixAccount_password']) && !empty($dataRow[$ids['posixAccount_password']])) {
2908						$temp['exop'][] = array($accounts[$i]['dn'], $dataRow[$ids['posixAccount_password']]);
2909					}
2910				}
2911			}
2912			$temp['dn_gon_keys'] = array_keys($temp['dn_gon']);
2913			return array(
2914				'status' => 'inProgress',
2915				'progress' => 0,
2916				'errors' => array()
2917			);
2918		}
2919		// get DNs of groups
2920		elseif (!isset($temp['dn'])) {
2921			$temp['dn'] = array();
2922			$ldapEntries = searchLDAPByAttribute('cn', '*', 'posixGroup', array('dn', 'cn'), array('group'));
2923			for ($i = 0; $i < sizeof($ldapEntries); $i++) {
2924				$temp['dn'][$ldapEntries[$i]['cn'][0]] = $ldapEntries[$i]['dn'];
2925			}
2926			return array(
2927				'status' => 'inProgress',
2928				'progress' => 0,
2929				'errors' => array()
2930			);
2931		}
2932		// add users to groups
2933		elseif ($temp['counter'] < sizeof($temp['groups'])) {
2934			if (isset($temp['dn'][$temp['groups'][$temp['counter']]])) {
2935				$memberUid = $temp['members'][$temp['groups'][$temp['counter']]];
2936				$dnToUpdate = $temp['dn'][$temp['groups'][$temp['counter']]];
2937				$groupAttrs = ldapGetDN($dnToUpdate, array('memberUID'));
2938				if (!empty($groupAttrs['memberuid'])) {
2939					// skip members that are already set
2940					$memberUid = array_delete($groupAttrs['memberuid'], $memberUid);
2941				}
2942				if (!empty($memberUid)) {
2943					$toAdd = array('memberUID' => $memberUid);
2944					$success = @ldap_mod_add($_SESSION['ldap']->server(), $dnToUpdate, $toAdd);
2945					$errors = array();
2946					if (!$success) {
2947						$errors[] = array(
2948							"ERROR",
2949							_("LAM was unable to modify group memberships for group: %s"),
2950							getDefaultLDAPErrorString($_SESSION['ldap']->server()),
2951							array($temp['groups'][$temp['counter']])
2952						);
2953					}
2954				}
2955				$temp['counter']++;
2956				return array (
2957					'status' => 'inProgress',
2958					'progress' => ($temp['counter'] * 100) / (sizeof($temp['groups']) + sizeof($temp['createHomes']) + sizeof($temp['dn_gon']) + sizeof($temp['exop'])),
2959					'errors' => $errors
2960				);
2961			}
2962			else {
2963				$temp['counter']++;
2964				return array (
2965					'status' => 'inProgress',
2966					'progress' => ($temp['counter'] * 100) / (sizeof($temp['groups'] + sizeof($temp['createHomes']) + sizeof($temp['dn_gon']) + sizeof($temp['exop']))),
2967					'errors' => array(array('ERROR', _('Unable to find group in LDAP.'), $temp['groups'][$temp['counter']]))
2968				);
2969			}
2970		}
2971		// create home directories
2972		elseif ($temp['counter'] < (sizeof($temp['groups']) + sizeof($temp['createHomes']))) {
2973			$pos = $temp['createHomes'][$temp['counter'] - sizeof($temp['groups'])];
2974			try {
2975				$remote = new \LAM\REMOTE\Remote();
2976				$remoteServer = $_SESSION['config']->getScriptServerByName($data[$pos][$ids['posixAccount_createHomeDir']]);
2977				$remote->connect($remoteServer);
2978				$result = $remote->execute(
2979					implode(
2980						self::$SPLIT_DELIMITER,
2981						array(
2982							$data[$pos][$ids['posixAccount_userName']],
2983							"home",
2984							"add",
2985							$remoteServer->getHomeDirPrefix() . $accounts[$pos][$homeDirAttr],
2986							"0".$_SESSION['config']->get_scriptRights(),
2987							$accounts[$pos]['uidNumber'],
2988							$accounts[$pos]['gidNumber'],
2989						)
2990					));
2991				$remote->disconnect();
2992				$errors = array();
2993				if (!empty($result)) {
2994					$parts = explode(",", $result);
2995					if (in_array($parts[0], array('ERROR', 'WARN'))) {
2996						$errors[] = $parts;
2997					}
2998				}
2999			}
3000			catch (LAMException $e) {
3001				$errors[] = array('ERROR', $e->getTitle(), $e->getMessage());
3002			}
3003			$temp['counter']++;
3004			return array (
3005				'status' => 'inProgress',
3006				'progress' => ($temp['counter'] * 100) / (sizeof($temp['groups']) + sizeof($temp['createHomes']) + sizeof($temp['dn_gon']) + sizeof($temp['exop'])),
3007				'errors' => $errors
3008			);
3009		}
3010		// add users to group of names
3011		elseif ($temp['counter'] < (sizeof($temp['groups']) + sizeof($temp['createHomes']) + sizeof($temp['dn_gon']))) {
3012			$gonDn = $temp['dn_gon_keys'][$temp['counter'] - sizeof($temp['groups']) - sizeof($temp['createHomes'])];
3013			$gonAttrToAdd = $temp['dn_gon'][$gonDn];
3014			$gonAttrNames = array_keys($gonAttrToAdd);
3015			$gonAttrs = ldapGetDN($gonDn, $gonAttrNames);
3016			foreach ($gonAttrNames as $gonAttrName) {
3017				$gonAttrNameLower = strtolower($gonAttrName);
3018				if (!empty($gonAttrs[$gonAttrNameLower])) {
3019					$gonAttrToAdd[$gonAttrName] = array_delete($gonAttrs[$gonAttrNameLower], $gonAttrToAdd[$gonAttrName]);
3020				}
3021				if (empty($gonAttrToAdd[$gonAttrName])) {
3022					unset($gonAttrToAdd[$gonAttrName]);
3023				}
3024			}
3025			if (!empty($gonAttrToAdd)) {
3026				$success = @ldap_mod_add($_SESSION['ldap']->server(), $gonDn, $gonAttrToAdd);
3027				$errors = array();
3028				if (!$success) {
3029					$errors[] = array(
3030						"ERROR",
3031						_("LAM was unable to modify group memberships for group: %s"),
3032						getDefaultLDAPErrorString($_SESSION['ldap']->server()),
3033						array($temp['groups'][$temp['counter']])
3034					);
3035				}
3036			}
3037			$temp['counter']++;
3038			return array (
3039				'status' => 'inProgress',
3040				'progress' => ($temp['counter'] * 100) / (sizeof($temp['groups']) + sizeof($temp['createHomes']) + sizeof($temp['dn_gon']) + sizeof($temp['exop'])),
3041				'errors' => $errors
3042			);
3043		}
3044		// run password exop commands
3045		elseif ($temp['counter'] < (sizeof($temp['groups']) + sizeof($temp['createHomes']) + sizeof($temp['dn_gon']) + sizeof($temp['exop']))) {
3046			$data = $temp['exop'][$temp['counter'] - sizeof($temp['groups']) - sizeof($temp['createHomes']) - sizeof($temp['dn_gon'])];
3047			$dn = $data[0];
3048			$password = $data[1];
3049			$success = ldap_exop_passwd($_SESSION['ldap']->server(), $dn, null, $password);
3050			$errors = array();
3051			if (!$success) {
3052				$errors[] = array(
3053					"ERROR",
3054					_('Unable to set password'),
3055					$dn . '<br>' . getDefaultLDAPErrorString($_SESSION['ldap']->server()),
3056					array($temp['groups'][$temp['counter']])
3057				);
3058			}
3059			$temp['counter']++;
3060			return array (
3061				'status' => 'inProgress',
3062				'progress' => ($temp['counter'] * 100) / (sizeof($temp['groups']) + sizeof($temp['createHomes']) + sizeof($temp['dn_gon']) + sizeof($temp['exop'])),
3063				'errors' => $errors
3064			);
3065		}
3066		// all modifications are done
3067		else {
3068			return array (
3069				'status' => 'finished',
3070				'progress' => 100,
3071				'errors' => array()
3072			);
3073		}
3074	}
3075
3076	/**
3077	* Returns one or more free UID numbers.
3078	*
3079	* @param integer $count Number of needed free UIDs.
3080	* @param array $errors list of error messages where errors can be added
3081	* @param string $typeId type id (e.g. user)
3082	* @return mixed Null if no UIDs are free else an array of free UIDs.
3083	*/
3084	function getNextUIDs($count, &$errors, $typeId) {
3085		// check if UIDs should be taken from Samba pool entry
3086		if (($this->get_scope() == 'user') && isset($this->moduleSettings['posixAccount_' . $typeId . '_uidGeneratorUsers']) && ($this->moduleSettings['posixAccount_' . $typeId . '_uidGeneratorUsers'][0] == 'sambaPool')) {
3087			return $this->getNextSambaPoolUIDs($count, $errors, $typeId);
3088		}
3089		if (($this->get_scope() == 'host') && isset($this->moduleSettings['posixAccount_' . $typeId . '_uidGeneratorHosts']) && ($this->moduleSettings['posixAccount_' . $typeId . '_uidGeneratorHosts'][0] == 'sambaPool')) {
3090			return $this->getNextSambaPoolUIDs($count, $errors, $typeId);
3091		}
3092		// check if UIDs should be taken from domain info pool entry
3093		if (($this->get_scope() == 'user') && isset($this->moduleSettings['posixAccount_' . $typeId . '_uidGeneratorUsers']) && ($this->moduleSettings['posixAccount_' . $typeId . '_uidGeneratorUsers'][0] == 'windowsDomain')) {
3094			return $this->getNextDomainInfoUIDs($count, $errors, $typeId);
3095		}
3096		if (($this->get_scope() == 'host') && isset($this->moduleSettings['posixAccount_' . $typeId . '_uidGeneratorHosts']) && ($this->moduleSettings['posixAccount_' . $typeId . '_uidGeneratorHosts'][0] == 'windowsDomain')) {
3097			return $this->getNextDomainInfoUIDs($count, $errors, $typeId);
3098		}
3099		// check if a magic number should be used
3100		if (($this->get_scope() == 'user') && isset($this->moduleSettings['posixAccount_' . $typeId . '_uidGeneratorUsers']) && ($this->moduleSettings['posixAccount_' . $typeId . '_uidGeneratorUsers'][0] == 'magicNumber')) {
3101			$return = array();
3102			for ($i = 0; $i < $count; $i++) {
3103				$return[] = $this->moduleSettings['posixAccount_' . $typeId . '_magicNumberUser'][0];
3104			}
3105			return $return;
3106		}
3107		if (($this->get_scope() == 'host') && isset($this->moduleSettings['posixAccount_' . $typeId . '_uidGeneratorHosts']) && ($this->moduleSettings['posixAccount_' . $typeId . '_uidGeneratorHosts'][0] == 'magicNumber')) {
3108			$return = array();
3109			for ($i = 0; $i < $count; $i++) {
3110				$return[] = $this->moduleSettings['posixAccount_' . $typeId . '_magicNumberHost'][0];
3111			}
3112			return $return;
3113		}
3114		$ret = array();
3115		if ($this->get_scope() == "user") {
3116			$minID = intval($this->moduleSettings['posixAccount_' . $typeId . '_minUID'][0]);
3117			$maxID = intval($this->moduleSettings['posixAccount_' . $typeId . '_maxUID'][0]);
3118		}
3119		else {
3120			$minID = intval($this->moduleSettings['posixAccount_' . $typeId . '_minMachine'][0]);
3121			$maxID = intval($this->moduleSettings['posixAccount_' . $typeId . '_maxMachine'][0]);
3122		}
3123		$uidList = $this->getUIDs($typeId);
3124		$uids = array();
3125		foreach ($uidList as $uid) {
3126			if (($uid <= $maxID) && ($uid >= $minID)) $uids[] = $uid;  // ignore UIDs > maxID and UIDs < minID
3127		}
3128		for ($i = 0; $i < $count; $i++) {
3129			if (count($uids) != 0) {
3130				// there already are some uids
3131				// store highest id-number
3132				$id = $uids[count($uids)-1];
3133				// Return minimum allowed id-number if all found id-numbers are too low
3134				if ($id < $minID) {
3135					$ret[] = $minID;
3136					$uids[] = $minID;
3137				}
3138				// return highest used id-number + 1 if it's still in valid range
3139				elseif ($id < $maxID) {
3140					$ret[] = $id + 1;
3141					$uids[] = $id + 1;
3142				}
3143				// find free numbers between existing ones
3144				else {
3145					$k = intval($minID);
3146					while (in_array($k, $uids)) $k++;
3147					if ($k > $maxID) return null;
3148					else {
3149						$ret[] = $k;
3150						$uids[] = $k;
3151						sort ($uids, SORT_NUMERIC);
3152					}
3153					// show warning message
3154					$errors[] = $this->messages['uidNumber'][2];
3155				}
3156			}
3157			else {
3158				// return minimum allowed id-number if no id-numbers are found
3159				$ret[] = $minID;
3160				$uids[] = $minID;
3161			}
3162		}
3163		return $ret;
3164	}
3165
3166	/**
3167	 * Gets the free UID numbers from an Samba pool entry in LDAP.
3168	 *
3169	 * @param integer $count number of needed free UIDs.
3170	 * @param array $errors list of error messages where errors can be added
3171	 * @param string $typeId type id (e.g. user)
3172	 * @return mixed null if no UIDs are free else an array of free UIDs
3173	 */
3174	private function getNextSambaPoolUIDs($count, &$errors, $typeId) {
3175		if ($this->get_scope() == 'user') {
3176			$dn = $this->moduleSettings['posixAccount_' . $typeId . '_sambaIDPoolDNUsers'][0];
3177		}
3178		else {
3179			$dn = $this->moduleSettings['posixAccount_' . $typeId . '_sambaIDPoolDNHosts'][0];
3180		}
3181		$attrs = ldapGetDN($dn, array('uidNumber'));
3182		if (isset($attrs['uidnumber'][0]) && ($attrs['uidnumber'][0] != '')) {
3183			$newValue = $attrs['uidnumber'][0] + $count;
3184			$ldapHandle = $_SESSION['ldap']->server();
3185			ldap_modify($ldapHandle, $dn, array('uidnumber' => array($newValue)));
3186			logNewMessage(LOG_DEBUG, 'Updated Samba ID pool ' . $dn . ' with UID number ' . $newValue . ' and LDAP code ' . ldap_errno($ldapHandle));
3187			if (ldap_errno($ldapHandle) != 0) {
3188				logNewMessage(LOG_NOTICE, 'Updating Samba ID pool ' . $dn . ' with UID number ' . $newValue . ' failed. ' . ldap_error($ldapHandle));
3189				return null;
3190			}
3191			$result = array();
3192			for ($i = 0; $i < $count; $i++) {
3193				$result[] = $attrs['uidnumber'][0] + $i;
3194			}
3195			return $result;
3196		}
3197		return null;
3198	}
3199
3200	/**
3201	 * Gets the free UID numbers from a domain info entry in LDAP.
3202	 *
3203	 * @param integer $count number of needed free UIDs.
3204	 * @param array $errors list of error messages where errors can be added
3205	 * @param string $typeId type id (e.g. user)
3206	 * @return mixed null if no UIDs are free else an array of free UIDs
3207	 */
3208	private function getNextDomainInfoUIDs($count, &$errors, $typeId) {
3209		if ($this->get_scope() == 'user') {
3210			$dn = $this->moduleSettings['posixAccount_' . $typeId . '_windowsIDPoolDNUsers'][0];
3211		}
3212		else {
3213			$dn = $this->moduleSettings['posixAccount_' . $typeId . '_windowsIDPoolDNHosts'][0];
3214		}
3215		$attrs = ldapGetDN($dn, array('msSFU30MaxUidNumber'));
3216		if (!empty($attrs['mssfu30maxuidnumber'][0])) {
3217			$newValue = $attrs['mssfu30maxuidnumber'][0] + $count;
3218			$ldapHandle = $_SESSION['ldap']->server();
3219			ldap_modify($ldapHandle, $dn, array('mssfu30maxuidnumber' => array($newValue)));
3220			logNewMessage(LOG_DEBUG, 'Updated domain info ' . $dn . ' with UID number ' . $newValue . ' and LDAP code ' . ldap_errno($ldapHandle));
3221			if (ldap_errno($ldapHandle) != 0) {
3222				logNewMessage(LOG_NOTICE, 'Updating domain info ' . $dn . ' with UID number ' . $newValue . ' failed. ' . ldap_error($ldapHandle));
3223				return null;
3224			}
3225			$result = array();
3226			for ($i = 0; $i < $count; $i++) {
3227				$result[] = $attrs['mssfu30maxuidnumber'][0] + $i;
3228			}
3229			return $result;
3230		}
3231		return null;
3232	}
3233
3234	/**
3235	 * Returns the meta HTML code for each input field.
3236	 * format: array(<field1> => array(<META HTML>), ...)
3237	 * It is not possible to display help links.
3238	 *
3239	 * @param array $fields list of active fields
3240	 * @param array $attributes attributes of LDAP account
3241	 * @param boolean $passwordChangeOnly indicates that the user is only allowed to change his password and no LDAP content is readable
3242	 * @param array $readOnlyFields list of read-only fields
3243	 * @return array list of meta HTML elements (field name => htmlResponsiveRow)
3244	 */
3245	function getSelfServiceOptions($fields, $attributes, $passwordChangeOnly, $readOnlyFields) {
3246		$return = array();
3247		if (in_array('password', $fields)) {
3248			$row = new htmlResponsiveRow();
3249			if (!empty($this->selfServiceSettings->moduleSettings['posixAccount_useOldPwd']) && ($this->selfServiceSettings->moduleSettings['posixAccount_useOldPwd'][0] == 'true')) {
3250				$pwd0 = new htmlResponsiveInputField(_('Old password'), 'posixAccount_passwordOld');
3251				$pwd0->setIsPassword(true, true);
3252				$row->add($pwd0, 12);
3253			}
3254			$pwd1 = new htmlResponsiveInputField($this->getSelfServiceLabel('password', _('New password')), 'posixAccount_password');
3255			$pwd1->setIsPassword(true, true);
3256			$row->add($pwd1, 12);
3257			$pwd2 = new htmlResponsiveInputField(_('Reenter password'), 'posixAccount_password2');
3258			$pwd2->setIsPassword(true);
3259			$pwd2->setSameValueFieldID('posixAccount_password');
3260			$row->add($pwd2, 12);
3261			$return['password'] = $row;
3262		}
3263		if ($passwordChangeOnly) {
3264			return $return; // only password fields as long no LDAP content can be read
3265		}
3266		if (in_array('cn', $fields)) {
3267			$cn = '';
3268			if (isset($attributes['cn'][0])) {
3269				$cn = $attributes['cn'][0];
3270			}
3271			$cnField = new htmlInputField('posixAccount_cn', $cn);
3272			if (in_array('cn', $readOnlyFields)) {
3273				$cnField = new htmlOutputText($cn);
3274			}
3275			$row = new htmlResponsiveRow();
3276			$row->addLabel(new htmlOutputText($this->getSelfServiceLabel('cn', _('Common name'))));
3277			$row->addField($cnField);
3278			$return['cn'] = $row;
3279		}
3280		if (in_array('loginShell', $fields)) {
3281			$shelllist = $this->getShells(); // list of all valid shells
3282			$loginShell = '';
3283			if (isset($attributes['loginShell'][0])) {
3284				$loginShell = $attributes['loginShell'][0];
3285			}
3286			$loginShellField = new htmlSelect('posixAccount_loginShell', $shelllist, array($loginShell));
3287			if (in_array('loginShell', $readOnlyFields)) {
3288				$loginShellField = new htmlOutputText($loginShell);
3289			}
3290			$row = new htmlResponsiveRow();
3291			$row->addLabel(new htmlOutputText($this->getSelfServiceLabel('loginShell', _('Login shell'))));
3292			$row->addField($loginShellField);
3293			$return['loginShell'] = $row;
3294		}
3295		if (in_array('unixgroups', $fields) && !empty($this->selfServiceSettings->moduleSettings['posixAccount_groupDn'][0])) {
3296			$groupDn = $this->selfServiceSettings->moduleSettings['posixAccount_groupDn'][0];
3297			$gidNumber = $attributes['gidNumber'][0];
3298			$userName = $attributes['uid'][0];
3299			if (!empty($userName)) {
3300				$filter = '(&(objectClass=posixGroup)(|(gidNumber=' . $gidNumber . ')(memberUid=' . $userName . ')))';
3301				$results = searchLDAP($groupDn, $filter, array('cn'));
3302				$groups = array();
3303				foreach ($results as $result) {
3304					$groups[] = $result['cn'][0];
3305				}
3306				$groups = array_unique($groups);
3307				natcasesort($groups);
3308				$row = new htmlResponsiveRow();
3309				$row->addLabel(new htmlOutputText($this->getSelfServiceLabel('unixgroups', _('Groups'))));
3310				$row->addField(new htmlOutputText(implode(', ', $groups)));
3311				$return['unixgroups'] = $row;
3312			}
3313		}
3314		return $return;
3315	}
3316
3317	/**
3318	 * Checks if all input values are correct and returns the LDAP attributes which should be changed.
3319	 * <br>Return values:
3320	 * <br>messages: array of parameters to create status messages
3321	 * <br>add: array of attributes to add
3322	 * <br>del: array of attributes to remove
3323	 * <br>mod: array of attributes to modify
3324	 * <br>"info" are values with informational value (e.g. to be used later by pre/postModify actions)
3325	 *
3326	 * Calling this method does not require the existence of an enclosing {@link accountContainer}.
3327	 *
3328	 * @param string $fields input fields
3329	 * @param array $attributes LDAP attributes
3330	 * @param boolean $passwordChangeOnly indicates that the user is only allowed to change his password and no LDAP content is readable
3331	 * @param array $readOnlyFields list of read-only fields
3332	 * @return array messages and attributes (array('messages' => array(), 'add' => array('mail' => array('test@test.com')), 'del' => array(), 'mod' => array(), 'info' => array()))
3333	 */
3334	function checkSelfServiceOptions($fields, $attributes, $passwordChangeOnly, $readOnlyFields) {
3335		$return = array('messages' => array(), 'add' => array(), 'del' => array(), 'mod' => array(), 'info' => array());
3336		if (in_array('password', $fields)) {
3337			if (isset($_POST['posixAccount_password']) && ($_POST['posixAccount_password'] != '')) {
3338				if ($_POST['posixAccount_password'] != $_POST['posixAccount_password2']) {
3339					$return['messages'][] = $this->messages['userPassword'][0];
3340				}
3341				else {
3342					if (!get_preg($_POST['posixAccount_password'], 'password')) {
3343						$return['messages'][] = $this->messages['userPassword'][1];
3344					}
3345					else {
3346						$userName = empty($attributes['uid'][0]) ? null : $attributes['uid'][0];
3347						$additionalAttrs = array();
3348						if (!empty($attributes['sn'][0])) {
3349							$additionalAttrs[] = $attributes['sn'][0];
3350						}
3351						if (!empty($attributes['givenName'][0])) {
3352							$additionalAttrs[] = $attributes['givenName'][0];
3353						}
3354						$pwdPolicyResult = checkPasswordStrength($_POST['posixAccount_password'], $userName, $additionalAttrs);
3355						if ($pwdPolicyResult === true) {
3356							$passwordHash = $this->selfServiceSettings->moduleSettings['posixAccount_pwdHash'][0];
3357							if (empty($this->selfServiceSettings->moduleSettings['posixAccount_useOldPwd']) || ($this->selfServiceSettings->moduleSettings['posixAccount_useOldPwd'][0] != 'true')) {
3358								// set SASL password
3359								if (!empty($attributes['uid'][0]) && ($passwordHash === 'SASL')) {
3360									$return['mod']['userPassword'][0] = '{SASL}' . $attributes['uid'][0];
3361								}
3362								elseif ($passwordHash === 'LDAP_EXOP') {
3363									// no LDAP modify action, use ldap_exop_passwd
3364									$return['info']['userPasswordModify'][0] = 'exop';
3365								}
3366								// set other password hashes
3367								else {
3368									$return['mod']['userPassword'][0] = pwd_hash($_POST['posixAccount_password'], true, $passwordHash);
3369								}
3370
3371							}
3372							else {
3373								$return['add']['userPassword'][0] = pwd_hash($_POST['posixAccount_password'], true, $passwordHash);
3374								$return['del']['userPassword'][0] = $_POST['posixAccount_passwordOld'];
3375							}
3376							$return['info']['userPasswordClearText'][0] = $_POST['posixAccount_password'];
3377							if (isset($attributes['shadowLastChange'][0])) {
3378								$return['mod']['shadowLastChange'][0] = intval(time()/3600/24);
3379							}
3380							$_SESSION['selfService_clientPasswordNew'] = $_POST['posixAccount_password'];
3381						}
3382						else {
3383							$return['messages'][] = array('ERROR', $pwdPolicyResult);
3384						}
3385					}
3386				}
3387			}
3388		}
3389		// stop processing if only a password change is done
3390		if ($passwordChangeOnly) {
3391			return $return;
3392		}
3393		// sync from Windows password
3394		if (in_array('syncWindowsPassword', $fields) && !empty($_POST['windowsUser_unicodePwd'])) {
3395			$password = $_POST['windowsUser_unicodePwd'];
3396			$return['mod']['unixUserPassword'][0] = pwd_hash($password, true, $this->selfServiceSettings->moduleSettings['posixAccount_pwdHash'][0]);
3397			if (isset($attributes['shadowLastChange'][0])) {
3398				$return['mod']['shadowLastChange'][0] = intval(time()/3600/24);
3399			}
3400		}
3401		// cn
3402		if (in_array('cn', $fields) && !in_array('cn', $readOnlyFields)) {
3403			if (isset($_POST['posixAccount_cn']) && ($_POST['posixAccount_cn'] != '')) {
3404				if (!get_preg($_POST['posixAccount_cn'], 'cn')) {
3405					$return['messages'][] = $this->messages['cn'][0];
3406				}
3407				else if (!isset($attributes['cn']) || ($attributes['cn'][0] != $_POST['posixAccount_cn'])) {
3408					$return['mod']['cn'][0] = $_POST['posixAccount_cn'];
3409				}
3410			}
3411			else {
3412				$return['messages'][] = $this->messages['cn'][0];
3413			}
3414		}
3415		// shell
3416		if (in_array('loginShell', $fields) && !in_array('loginShell', $readOnlyFields)) {
3417			$shelllist = $this->getShells(); // list of all valid shells
3418			if (in_array($_POST['posixAccount_loginShell'], $shelllist)
3419					&& (!isset($attributes['loginShell']) || ($attributes['loginShell'][0] != $_POST['posixAccount_loginShell']))) {
3420				$return['mod']['loginShell'][0] = $_POST['posixAccount_loginShell'];
3421			}
3422		}
3423		return $return;
3424	}
3425
3426	/**
3427	 * {@inheritDoc}
3428	 * @see baseModule::postModifySelfService()
3429	 */
3430	public function postModifySelfService($newAccount, $attributes) {
3431		if (isset($attributes['INFO.userPasswordModify'][0])
3432			&& ($attributes['INFO.userPasswordModify'][0] === 'exop')) {
3433			$password = $attributes['INFO.userPasswordClearText'][0];
3434			$dn = $attributes['dn'][0];
3435			$success = ldap_exop_passwd($_SESSION['ldapHandle'], $dn, null, $password);
3436			if (!$success) {
3437				StatusMessage('ERROR', _('Unable to set password'), getExtendedLDAPErrorMessage($_SESSION['ldapHandle']));
3438			}
3439			else {
3440				StatusMessage('INFO', _('Password changed.'));
3441			}
3442			return $success;
3443		}
3444		return true;
3445	}
3446
3447	/**
3448	 * Returns if the module manages the password attribute.
3449	 *
3450	 * @param string $typeId account type id
3451	 * @return boolean manages password
3452	 */
3453	private function isPasswordManaged($typeId = null) {
3454		if ($typeId === null) {
3455			$typeId = $this->getAccountContainer()->get_type()->getId();
3456		}
3457		return !$this->isBooleanConfigOptionSet('posixAccount_' . $typeId . '_hidepassword');
3458	}
3459
3460	/**
3461	 * This method specifies if a module manages password attributes.
3462	 * @see passwordService::managesPasswordAttributes
3463	 *
3464	 * @return boolean true if this module manages password attributes
3465	 */
3466	public function managesPasswordAttributes() {
3467		return $this->isPasswordManaged();
3468	}
3469
3470	/**
3471	 * Specifies if this module supports to force that a user must change his password on next login.
3472	 *
3473	 * @return boolean force password change supported
3474	 */
3475	public function supportsForcePasswordChange() {
3476		return false;
3477	}
3478
3479	/**
3480	 * This function is called whenever the password should be changed. Account modules
3481	 * must change their password attributes only if the modules list contains their module name.
3482	 *
3483	 * @param String $password new password
3484	 * @param $modules list of modules for which the password should be changed
3485	 * @param boolean $forcePasswordChange force the user to change his password at next login
3486	 * @return array list of error messages if any as parameter array for StatusMessage
3487	 *               e.g. return array(array('ERROR', 'Password change failed.'))
3488	 * @see passwordService::passwordChangeRequested
3489	 */
3490	public function passwordChangeRequested($password, $modules, $forcePasswordChange) {
3491		if (!in_array(get_class($this), $modules)) {
3492			return array();
3493		}
3494		$accountModules = $this->getAccountContainer()->get_type()->getModules();
3495		// check password strength
3496		$user = empty($this->attributes['uid'][0]) ? null : $this->attributes['uid'][0];
3497		$additionalAttrs = array();
3498		if ($this->getAccountContainer()->getAccountModule('inetOrgPerson') != null) {
3499			$attrs = $this->getAccountContainer()->getAccountModule('inetOrgPerson')->getAttributes();
3500			if (!empty($attrs['sn'][0])) {
3501				$additionalAttrs[] = $attrs['sn'][0];
3502			}
3503			if (!empty($attrs['givenName'][0])) {
3504				$additionalAttrs[] = $attrs['givenName'][0];
3505			}
3506		}
3507		$checkResult = checkPasswordStrength($password, $user, $additionalAttrs);
3508		if ($checkResult !== true) {
3509			return array(array('ERROR', $checkResult));
3510		}
3511		// set new password
3512		$this->clearTextPassword = $password;
3513		// set SASL password
3514		if (!empty($this->attributes['uid'][0]) && !empty($this->moduleSettings['posixAccount_pwdHash'][0])
3515				&& ($this->moduleSettings['posixAccount_pwdHash'][0] === 'SASL')) {
3516			$this->attributes[$this->getPasswordAttrName($accountModules)][0] = '{SASL}' . $this->attributes['uid'][0];
3517		}
3518		// delay on ldap_exop
3519		elseif (!empty($this->moduleSettings['posixAccount_pwdHash'][0]) && ($this->moduleSettings['posixAccount_pwdHash'][0] === 'LDAP_EXOP')) {
3520			logNewMessage(LOG_DEBUG, 'Setting password in post action, exop');
3521		}
3522		// set normal password
3523		else {
3524			$this->attributes[$this->getPasswordAttrName($accountModules)][0] = pwd_hash($password, true, $this->moduleSettings['posixAccount_pwdHash'][0]);
3525		}
3526		return array();
3527	}
3528
3529	/**
3530	 * Returns the group ID of the given group.
3531	 *
3532	 * @param String $groupname group name
3533	 * @return String GID
3534	 */
3535	private function getGID($groupname) {
3536		$results = searchLDAPByAttribute('cn', $groupname, 'posixGroup', array('gidnumber'), array('group'));
3537		if ((sizeof($results) > 0) && isset($results[0]['gidnumber'][0])) {
3538			return $results[0]['gidnumber'][0];
3539		}
3540		return null;
3541	}
3542
3543	/**
3544	 * Returns the group name of the group with the given group ID.
3545	 *
3546	 * @param String $groupID group ID
3547	 * @return String group name
3548	 */
3549	private function getGroupName($groupID) {
3550		$results = searchLDAPByAttribute('gidNumber', $groupID, 'posixGroup', array('cn'), array('group'));
3551		if ((sizeof($results) > 0) && isset($results[0]['cn'][0])) {
3552			return $results[0]['cn'][0];
3553		}
3554		return null;
3555	}
3556
3557	/**
3558	 * Finds all existing LDAP groups.
3559	 *
3560	 * @return array groups array(array(gidnumber, cn), array(gidnumber, cn), ...)
3561	 */
3562	private function findGroups(&$modules) {
3563		if ($this->groupCache != null) {
3564			return $this->groupCache;
3565		}
3566		$this->groupCache = array();
3567		$typeManager = new TypeManager();
3568		foreach ($typeManager->getConfiguredTypesForScope('group') as $type) {
3569			$filter = '(objectClass=posixGroup)';
3570			if ($this->isWindows($modules)) {
3571				$filter = '(&(objectClass=group)(gidNumber=*))';
3572			}
3573			$typeFilter = $type->getAdditionalLdapFilter();
3574			if (!empty($typeFilter)) {
3575				if (strpos($typeFilter, '(') !== 0) {
3576					$typeFilter = '(' . $typeFilter . ')';
3577				}
3578				$filter = '(&' . $filter . $typeFilter . ')';
3579			}
3580			$results = searchLDAP($type->getSuffix(), $filter, array('cn', 'gidnumber'));
3581			for ($i = 0; $i < sizeof($results); $i++) {
3582				if (isset($results[$i]['cn'][0]) && isset($results[$i]['gidnumber'][0])) {
3583					$this->groupCache[] = array($results[$i]['gidnumber'][0], $results[$i]['cn'][0]);
3584				}
3585			}
3586		}
3587		return $this->groupCache;
3588	}
3589
3590	/**
3591	 * Finds all existing LDAP group of names.
3592	 *
3593	 * @return array groups array(dn => array('cn' => array('groupName'), 'objectclass' => array('top', 'groupOfNames')))
3594	 */
3595	public function findGroupOfNames() {
3596		if ($this->gonCache != null) {
3597			return $this->gonCache;
3598		}
3599		$return = array();
3600		$typeManager = new TypeManager();
3601		$types = $typeManager->getConfiguredTypesForScopes(array('gon', 'group'));
3602		foreach ($types as $type) {
3603			$typeFilter = get_ldap_filter($type->getId());
3604			$results = searchLDAP($type->getSuffix(), $typeFilter, array('cn', 'dn', 'objectClass'));
3605			for ($i = 0; $i < sizeof($results); $i++) {
3606				if ((in_array_ignore_case('groupOfNames', $results[$i]['objectclass'])
3607						|| in_array_ignore_case('groupOfMembers', $results[$i]['objectclass'])
3608						|| in_array_ignore_case('groupOfUniqueNames', $results[$i]['objectclass']))
3609						&& isset($results[$i]['cn'][0])) {
3610					$return[$results[$i]['dn']] = $results[$i];
3611				}
3612			}
3613		}
3614		$this->gonCache = $return;
3615		return $return;
3616	}
3617
3618	/**
3619	 * Returns a list of existing UID numbers.
3620	 *
3621	 * @param string $typeId type id (e.g. user)
3622	 * @return array list of UID numbers
3623	 */
3624	private function getUIDs($typeId) {
3625		if ($this->cachedUIDList != null) {
3626			return $this->cachedUIDList;
3627		}
3628		$this->cachedUIDList = array();
3629		$attrs = array('uidNumber');
3630		$filter = '(&(objectClass=posixAccount)(uidNumber=*))';
3631		if ($this->skipObjectClass()) {
3632			$filter = '(uidNumber=*)';
3633		}
3634		$typeManager = new TypeManager();
3635		$typesUser = $typeManager->getConfiguredTypesForScope('user');
3636		$typesHost = $typeManager->getConfiguredTypesForScope('host');
3637		$suffixes = array();
3638		if (!empty($typesUser)) {
3639			if (!empty($this->moduleSettings['posixAccount_' . $typeId . '_uidCheckSuffixUser'][0])) {
3640				$suffixes[] = $this->moduleSettings['posixAccount_' . $typeId . '_uidCheckSuffixUser'][0];
3641			}
3642			else {
3643				foreach ($typesUser as $type) {
3644					$suffixes[] = $type->getSuffix();
3645				}
3646			}
3647		}
3648		if (!empty($typesHost)) {
3649			if (!empty($this->moduleSettings['posixAccount_' . $typeId . '_uidCheckSuffixHost'][0])) {
3650				$suffixes[] = $this->moduleSettings['posixAccount_' . $typeId . '_uidCheckSuffixHost'][0];
3651			}
3652			else {
3653				foreach ($typesHost as $type) {
3654					$suffixes[] = $type->getSuffix();
3655				}
3656			}
3657		}
3658		$suffixes = array_unique($suffixes);
3659		foreach ($suffixes as $suffix) {
3660			$result = searchLDAP($suffix, $filter, $attrs);
3661			foreach ($result as $resultEntry) {
3662				$this->cachedUIDList[] = $resultEntry['uidnumber'][0];
3663			}
3664		}
3665		$this->cachedUIDList = array_values(array_unique($this->cachedUIDList));
3666		sort($this->cachedUIDList, SORT_NUMERIC);
3667		return $this->cachedUIDList;
3668	}
3669
3670	/**
3671	 * Checks if the given user name already exists in LDAP.
3672	 *
3673	 * @param String $userName user name
3674	 * @param string $typeId type id (e.g. user)
3675	 * @return boolean true if already exists
3676	 */
3677	private function userNameExists($userName, $typeId) {
3678		return array_key_exists($userName, $this->getUserNames($typeId));
3679	}
3680
3681	/**
3682	 * Returns a list of all user names in LDAP.
3683	 *
3684	 * @param string $typeId type id (e.g. user)
3685	 * @return array user names
3686	 */
3687	private function getUserNames($typeId) {
3688		if ($this->cachedUserNameList != null) {
3689			return $this->cachedUserNameList;
3690		}
3691		$this->cachedUserNameList = array();
3692		$attrs = array('uid');
3693		$filter = '(&(objectClass=posixAccount)(uid=*))';
3694		if ($this->skipObjectClass()) {
3695			$filter = '(uid=*)';
3696		}
3697		$typeManager = new TypeManager();
3698		$typesUser = $typeManager->getConfiguredTypesForScope('user');
3699		$typesHost = $typeManager->getConfiguredTypesForScope('host');
3700		$suffixes = array();
3701		if (!empty($typesUser)) {
3702			if (!empty($this->moduleSettings['posixAccount_' . $typeId . '_uidCheckSuffixUser'][0])) {
3703				$suffixes[] = $this->moduleSettings['posixAccount_' . $typeId . '_uidCheckSuffixUser'][0];
3704			}
3705			else {
3706				foreach ($typesUser as $type) {
3707					$suffixes[] = $type->getSuffix();
3708				}
3709			}
3710		}
3711		if (!empty($typesHost)) {
3712			if (!empty($this->moduleSettings['posixAccount_' . $typeId . '_uidCheckSuffixHost'][0])) {
3713				$suffixes[] = $this->moduleSettings['posixAccount_' . $typeId . '_uidCheckSuffixHost'][0];
3714			}
3715			else {
3716				foreach ($typesHost as $type) {
3717					$suffixes[] = $type->getSuffix();
3718				}
3719			}
3720		}
3721		$suffixes = array_unique($suffixes);
3722		foreach ($suffixes as $suffix) {
3723			$result = searchLDAP($suffix, $filter, $attrs);
3724			foreach ($result as $resultEntry) {
3725				$this->cachedUserNameList[$resultEntry['uid'][0]] = $resultEntry['dn'];
3726			}
3727		}
3728		return $this->cachedUserNameList;
3729	}
3730
3731	/**
3732	 * Returns if LAM manages group of names entries.
3733	 *
3734	 * @return boolean group of names are active
3735	 */
3736	public static function areGroupOfNamesActive() {
3737		if (!isset($_SESSION['config'])) {
3738			return false;
3739		}
3740		$typeManager = new TypeManager();
3741		$types = $typeManager->getConfiguredTypesForScopes(array('group', 'gon'));
3742		foreach ($types as $type) {
3743			$modules = $type->getModules();
3744			if (in_array('groupOfNames', $modules)
3745					|| in_array('groupOfMembers', $modules)
3746					|| in_array('groupOfUniqueNames', $modules)) {
3747				return true;
3748			}
3749		}
3750		return false;
3751	}
3752
3753	/**
3754	 * Returns a suggestion for the user name.
3755	 * By default this will be the first character of the first name plus the last name.
3756	 *
3757	 * @param array $attrs LDAP attributes
3758	 * @param string $typeId type id (e.g. user)
3759	 * @return String user name
3760	 */
3761	protected function getUserNameSuggestion($attrs, $typeId) {
3762		$attributes = array_change_key_case($attrs, CASE_LOWER);
3763		$format = '@givenname@%sn%';
3764		if (isset($this->moduleSettings['posixAccount_' . $typeId . '_userNameSuggestion'][0])) {
3765			$format = strtolower($this->moduleSettings['posixAccount_' . $typeId . '_userNameSuggestion'][0]);
3766		}
3767		// search for @key@ wildcards in format string and replace with first character of attribute
3768		$wildcards = array();
3769		if (preg_match_all('/@([^@]|[a-zA-Z_-])+@/', $format, $wildcards) > 0) {
3770			for ($i = 0; $i < sizeof($wildcards[0]); $i++) {
3771				$wc = substr($wildcards[0][$i], 1, strlen($wildcards[0][$i]) - 2);
3772				$value = '';
3773				if (isset($attributes[$wc][0]) && !empty($attributes[$wc][0])) {
3774					$value = $this->cleanSuggestionPart($attributes[$wc][0][0]);
3775				}
3776				$format = str_replace('@' . $wc . '@', $value, $format);
3777			}
3778		}
3779		// search for %key% wildcards in format string and replace with attribute
3780		$wildcards = array();
3781		if (preg_match_all('/%([^%]|[a-zA-Z_-])+%/', $format, $wildcards) > 0) {
3782			for ($i = 0; $i < sizeof($wildcards[0]); $i++) {
3783				$wc = substr($wildcards[0][$i], 1, strlen($wildcards[0][$i]) - 2);
3784				$value = '';
3785				if (isset($attributes[$wc][0])) {
3786					$value = $this->cleanSuggestionPart($attributes[$wc][0]);
3787				}
3788				$format = str_replace('%' . $wc . '%', $value, $format);
3789			}
3790		}
3791		return $format;
3792	}
3793
3794	/**
3795	 * Cleans a string that is injected in user name suggestion.
3796	 *
3797	 * @param string $part injected part
3798	 * @return string cleaned by removing umlauts, spaces, dashes and underscores
3799	 */
3800	private function cleanSuggestionPart($part) {
3801		$result = str_replace(array_keys($this->umlautReplacements), array_values($this->umlautReplacements), strtolower($part));
3802		return str_replace(array(' ', '_', '-'), array('', '', ''), $result);
3803	}
3804
3805	/**
3806	 * Returns if this account can be locked.
3807	 * This is the case if a hashed password is set ("{" at the beginning).
3808	 *
3809	 * @param string[] $modules account modules
3810	 * @return boolean lockable
3811	 */
3812	public function isLockable(&$modules) {
3813		if (isset($this->attributes[$this->getPasswordAttrName($modules)][0])
3814				&& pwd_is_lockable($this->attributes[$this->getPasswordAttrName($modules)][0])) {
3815			return true;
3816		}
3817		return false;
3818	}
3819
3820	/**
3821	 * Returns if the Unix part of the current account is locked.
3822	 *
3823	 * @param string[] $modules account modules
3824	 * @return boolean password is locked
3825	 */
3826	public function isLocked(&$modules) {
3827		return isset($this->attributes[$this->getPasswordAttrName($modules)][0])
3828			&& !pwd_is_enabled($this->attributes[$this->getPasswordAttrName($modules)][0]);
3829	}
3830
3831	/**
3832	 * Locks the user password of this account.
3833	 *
3834	 * @param string[] $modules account modules
3835	 */
3836	public function lock(&$modules) {
3837		$pwdAttrName = $this->getPasswordAttrName($modules);
3838		if (isset($this->attributes[$pwdAttrName][0])) {
3839			$this->attributes[$pwdAttrName][0] = pwd_disable($this->attributes[$pwdAttrName][0]);
3840		}
3841	}
3842
3843	/**
3844	 * Unlocks the user password of this account.
3845	 *
3846	 * @param string[] $modules account modules
3847	 */
3848	public function unlock(&$modules) {
3849		$pwdAttrName = $this->getPasswordAttrName($modules);
3850		if (isset($this->attributes[$pwdAttrName][0])) {
3851			$this->attributes[$pwdAttrName][0] = pwd_enable($this->attributes[$pwdAttrName][0]);
3852		}
3853	}
3854
3855	/**
3856	 * Removes all Unix group memberships from this user.
3857	 */
3858	public function removeFromUnixGroups() {
3859		$this->groups = array();
3860	}
3861
3862	/**
3863	 * Removes all group of names memberships from this user.
3864	 */
3865	public function removeFromGONGroups() {
3866		$this->gonList = array();
3867	}
3868
3869	/**
3870	 * Returns the next possible user name based on the given one.
3871	 * If the user name does not end with a number then a "2" is added.
3872	 * User names with numbers at the end are simply increased by one.
3873	 * <br>
3874	 * <br>Attention: This user name might still be in use. This needs to be checked separately.
3875	 *
3876	 * @param String $userName user name
3877	 * @param string[] $moduleNames list of account module names
3878	 * @return String new user name
3879	 */
3880	protected function getNextUserName($userName, $moduleNames) {
3881		if ($this->get_scope() == 'host' && in_array('sambaSamAccount', $moduleNames)) {
3882			$userName = substr($userName, 0, -1);
3883		}
3884		// get last character of username
3885		$lastchar = substr($userName, strlen($userName) - 1, 1);
3886		$suffix = '';
3887		if (($this->get_scope() == 'host') && in_array('sambaSamAccount', $moduleNames)) {
3888			$suffix = '$';
3889		}
3890		// Last character is no number
3891		if ( !preg_match('/^([0-9])+$/', $lastchar)) {
3892			// Last character is no number. Therefore we only have to add "2" to it.
3893			$userName = $userName . '2' . $suffix;
3894		}
3895		else {
3896			/* Last character is a number -> we have to increase the number until we've
3897			* found a groupname with trailing number which is not in use.
3898			*
3899			* $i will show us were we have to split groupname so we get a part
3900			* with the groupname and a part with the trailing number
3901			*/
3902			$i = strlen($userName) - 1;
3903			$mark = false;
3904			// Set $i to the last character which is a number in $account_new->general_username
3905			while (!$mark) {
3906				if (preg_match('/^([0-9])+$/', substr($userName, $i, strlen($userName) - $i))) {
3907					$i--;
3908				}
3909				else {
3910					$mark=true;
3911				}
3912			}
3913			// increase last number with one
3914			$firstchars = substr($userName, 0, $i + 1);
3915			$lastchars = substr($userName, $i + 1, strlen($userName) - $i);
3916			// Put username together
3917			$userName = $firstchars . (intval($lastchars) + 1) . $suffix;
3918		}
3919		return $userName;
3920	}
3921
3922	/**
3923	 * Returns the list of possible login shells.
3924	 *
3925	 * @return array login shells
3926	 */
3927	private function getShells() {
3928		// self service
3929		if (!isLoggedIn() && isset($this->selfServiceSettings) && isset($this->selfServiceSettings->moduleSettings['posixAccount_shells'])
3930			&& (sizeof($this->selfServiceSettings->moduleSettings['posixAccount_shells'])) > 0) {
3931			return $this->selfServiceSettings->moduleSettings['posixAccount_shells'];
3932		}
3933		// server profile
3934		if (!isset($this->selfServiceSettings) && isset($this->moduleSettings) && isset($this->moduleSettings['posixAccount_shells'])
3935			&& (sizeof($this->moduleSettings['posixAccount_shells'])) > 0) {
3936			return $this->moduleSettings['posixAccount_shells'];
3937		}
3938		// fall back to default
3939		return array(
3940			'/bin/bash',
3941			'/bin/csh',
3942			'/bin/dash',
3943			'/bin/false',
3944			'/bin/ksh',
3945			'/bin/sh'
3946		);
3947	}
3948
3949	/**
3950	 * Returns if the cn attribute should be managed.
3951	 * If Windows modules are active then cn will not be managed.
3952	 *
3953	 * @param string[] $modules account modules
3954	 * @return boolean manage cn attribute
3955	 */
3956	private function manageCn(&$modules) {
3957		return !$this->isWindows($modules);
3958	}
3959
3960	/**
3961	 * Returns if the Unix part can be added and removed.
3962	 *
3963	 * @param string[] $modules account modules
3964	 * @return boolean is optional
3965	 */
3966	private function isOptional(&$modules) {
3967		return !$this->manageCn($modules);
3968	}
3969
3970	/**
3971	 * Returns if the Windows module is active.
3972	 *
3973	 * @param string[] $modules account modules
3974	 * @return boolean is Windows
3975	 */
3976	private function isWindows(&$modules) {
3977		return in_array('windowsUser', $modules);
3978	}
3979
3980	/**
3981	 * Returns the password attribute.
3982	 * Usually, this is userPassword. If Windows modules are active this is unixUserPassword.
3983	 *
3984	 * @param string[] $modules account modules
3985	 * @return boolean attribute name
3986	 */
3987	private function getPasswordAttrName(&$modules) {
3988		if ($this->isWindows($modules)) {
3989			return 'unixUserPassword';
3990		}
3991		return 'userPassword';
3992	}
3993
3994	/**
3995	 * Returns the home directory attribute.
3996	 * Usually, this is homeDirectory. If Windows modules are active this is unixHomeDirectory.
3997	 *
3998	 * @param string[] $modules account modules
3999	 * @return boolean attribute name
4000	 */
4001	private function getHomedirAttrName(&$modules) {
4002		if ($this->isWindows($modules)) {
4003			return 'unixHomeDirectory';
4004		}
4005		return 'homeDirectory';
4006	}
4007
4008	/**
4009	 * Syncs the group of names with groups.
4010	 */
4011	private function syncGonToGroups() {
4012		$this->groups = array();
4013		$allGons = $this->findGroupOfNames();
4014		foreach ($this->gonList as $dn) {
4015			if (!isset($allGons[$dn])) {
4016				continue;
4017			}
4018			$gon = $this->gonCache[$dn];
4019			if (in_array_ignore_case('posixGroup', $gon['objectclass']) && !empty($gon['cn'])) {
4020				$this->groups[] = $gon['cn'][0];
4021			}
4022		}
4023	}
4024
4025	/**
4026	 * Returns if the object class should not be added.
4027	 *
4028	 * @return do not add
4029	 */
4030	private function skipObjectClass() {
4031		return $this->isBooleanConfigOptionSet('posixAccount_noObjectClass');
4032	}
4033
4034	/**
4035	 * {@inheritdoc}
4036	 */
4037	public function getWildCardReplacements() {
4038		$replacements = array();
4039		// user name
4040		if (!empty($_POST['uid'])) {
4041			$replacements['user'] = $_POST['uid'];
4042		}
4043		elseif (!empty($this->attributes['uid'][0])) {
4044			$replacements['user'] = $this->attributes['uid'][0];
4045		}
4046		// group name
4047		if (!empty($_POST['gidNumber'])) {
4048			$replacements['group'] = $this->getGroupName($_POST['gidNumber']);
4049		}
4050		elseif (!empty($this->attributes['gidNumber'][0])) {
4051			$replacements['group'] = $this->getGroupName($this->attributes['gidNumber'][0]);
4052		}
4053		return $replacements;
4054	}
4055
4056	/**
4057	 * Returns the current group names.
4058	 *
4059	 * @return string[] group names
4060	 */
4061	public function getGroups() {
4062		return $this->groups;
4063	}
4064
4065	/**
4066	 * Returns the list of group of names where this user is member.
4067	 *
4068	 * @return string[] list of DNs
4069	 */
4070	public function getGroupOfNames() {
4071		return $this->gonList;
4072	}
4073
4074}
4075
4076?>
4077