<?php
/*

  This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
  Copyright (C) 2011 - 2020  Roland Gruber

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/**
* Manages FreeRadius accounts.
*
* @package modules
* @author Roland Gruber
*/

/**
* Manages FreeRadius accounts.
*
* @package modules
*/
class freeRadius extends baseModule {

	/** list of possible months */
	private static $monthList = array('01' => 'Jan', '02' => 'Feb', '03' => 'Mar', '04' => 'Apr', '05' => 'May',
			'06' => 'Jun', '07' => 'Jul', '08' => 'Aug', '09' => 'Sep', '10' => 'Oct', '11' => 'Nov', '12' => 'Dec'
		);

	/** cache for profile DNs */
	private $profileCache = null;


	/**
	* Creates a new freeRadius object.
	*
	* @param string $scope account type (user, group, host)
	*/
	function __construct($scope) {
		parent::__construct($scope);
		$this->autoAddObjectClasses = false;
	}

	/**
	* Returns true if this module can manage accounts of the current type, otherwise false.
	*
	* @return boolean true if module fits
	*/
	public function can_manage() {
		return in_array($this->get_scope(), array('user'));
	}

	/**
	* Returns meta data that is interpreted by parent class
	*
	* @return array array with meta data
	*
	* @see baseModule::get_metaData()
	*/
	function get_metaData() {
		$return = array();
		// icon
		$return['icon'] = 'freeRadius.png';
		// alias name
		$return["alias"] = _("FreeRadius");
		// module dependencies
		$return['dependencies'] = array('depends' => array(array('posixAccount', 'inetOrgPerson')), 'conflicts' => array());
		// managed object classes
		$return['objectClasses'] = array('radiusprofile');
		// managed attributes
		$return['attributes'] = array('radiusFramedIPAddress', 'radiusFramedIPNetmask', 'radiusRealm', 'radiusGroupName',
			'radiusExpiration', 'radiusIdleTimeout', 'dialupAccess', 'radiusProfileDn');
		// help Entries
		$return['help'] = array(
			'radiusFramedIPAddress' => array(
				"Headline" => _("IP address"), 'attr' => 'radiusFramedIPAddress',
				"Text" => _("This is the IP address for the user (e.g. 123.123.123.123).")
			),
			'radiusFramedIPNetmask' => array(
				"Headline" => _("Net mask"), 'attr' => 'radiusFramedIPNetmask',
				"Text" => _("The net mask for the IP address.")
			),
			'radiusRealm' => array(
				"Headline" => _("Realm"), 'attr' => 'radiusRealm',
				"Text" => _("The Radius realm of this account.")
			),
			'radiusGroupName' => array(
				"Headline" => _("Group names"), 'attr' => 'radiusGroupName',
				"Text" => _("The group names for this account.")
			),
			'radiusGroupNameList' => array(
				"Headline" => _("Group names"), 'attr' => 'radiusGroupName',
				"Text" => _("The group names for this account.") . ' ' . _("Multiple values are separated by semicolon.")
			),
			'radiusExpiration' => array(
				"Headline" => _("Expiration date"), 'attr' => 'radiusExpiration',
				"Text" => _("The account will be locked after this date.")
			),
			'radiusIdleTimeout' => array(
				"Headline" => _("Idle timeout"), 'attr' => 'radiusIdleTimeout',
				"Text" => _("Specifies the maximum number of seconds that a connection can be idle before the session is terminated.")
			),
			'dialupAccess' => array(
				"Headline" => _("Enabled"), 'attr' => 'dialupAccess',
				"Text" => _("Specifies if the user may authenticate with FreeRadius.")
			),
			'profileDN' => array(
				"Headline" => _("Profile DN"), 'attr' => 'radiusProfileDn',
				"Text" => _('DN where Radius profile templates are stored.')
			),
			'radiusProfileDn' => array(
				"Headline" => _("Profile"), 'attr' => 'radiusProfileDn',
				"Text" => _('Radius profile for this user.')
			),
			'hiddenOptions' => array(
				"Headline" => _("Hidden options"),
				"Text" => _("The selected options will not be managed inside LAM. You can use this to reduce the number of displayed input fields.")
			));
		// profile settings
		$profileElements = array();
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusFramedIPNetmask')) {
			$profileElements[] = new htmlResponsiveInputField(_('Net mask'), 'freeRadius_radiusFramedIPNetmask', null, 'radiusFramedIPNetmask');
			$return['profile_checks']['freeRadius_radiusFramedIPNetmask'] = array(
				'type' => 'ext_preg',
				'regex' => 'ip',
				'error_message' => $this->messages['radiusFramedIPNetmask'][0]);
			$return['profile_mappings']['freeRadius_radiusFramedIPNetmask'] = 'radiusFramedIPNetmask';
		}
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusRealm')) {
			$profileElements[] = new htmlResponsiveInputField(_('Realm'), 'freeRadius_radiusRealm', null, 'radiusRealm');
			$return['profile_checks']['freeRadius_radiusRealm'] = array(
				'type' => 'ext_preg',
				'regex' => 'DNSname',
				'error_message' => $this->messages['radiusRealm'][0]);
			$return['profile_mappings']['freeRadius_radiusRealm'] = 'radiusRealm';
		}
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusGroupName')) {
			$profileElements[] = new htmlResponsiveInputField(_('Group names'), 'freeRadius_radiusGroupName', null, 'radiusGroupNameList');
		}
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusIdleTimeout')) {
			$profileElements[] = new htmlResponsiveInputField(_('Idle timeout'), 'freeRadius_radiusIdleTimeout', null, 'radiusIdleTimeout');
			$return['profile_checks']['freeRadius_radiusIdleTimeout'] = array(
				'type' => 'ext_preg',
				'regex' => 'digit',
				'error_message' => $this->messages['radiusIdleTimeout'][0]);
			$return['profile_mappings']['freeRadius_radiusIdleTimeout'] = 'radiusIdleTimeout';
		}
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideDialupAccess')) {
			$enabledOptions = array('-' => '', _('Yes') => 'true', _('No') => 'false');
			$dialupAccessSelect = new htmlResponsiveSelect('freeRadius_dialupAccess', $enabledOptions, array('true'), _('Enabled'), 'dialupAccess');
			$dialupAccessSelect->setHasDescriptiveElements(true);
			$profileElements[] = $dialupAccessSelect;
			$return['profile_mappings']['freeRadius_dialupAccess'] = 'dialupAccess';
		}
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusProfileDn') && isLoggedIn()) {
			$profileOptions = array('-' => '');
			foreach ($this->getProfiles() as $dn) {
				$profileOptions[getAbstractDN($dn)] = $dn;
			}
			$profileSelect = new htmlResponsiveSelect('freeRadius_radiusProfileDn', $profileOptions, array(''), _('Profile'), 'radiusProfileDn');
			$profileSelect->setHasDescriptiveElements(true);
			$profileElements[] = $profileSelect;
			$return['profile_mappings']['freeRadius_radiusProfileDn'] = 'radiusProfileDn';
		}
		if (sizeof($profileElements) > 0) {
			$profileContainer = new htmlResponsiveRow();
			for ($i = 0; $i < sizeof($profileElements); $i++) {
				$profileContainer->add($profileElements[$i], 12);
			}
			$return['profile_options'] = $profileContainer;
		}
		// upload fields
		$return['upload_columns'] = array();
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusRealm')) {
			$return['upload_columns'][] = array(
				'name' => 'freeRadius_radiusRealm',
				'description' => _('Realm'),
				'help' => 'radiusRealm',
				'example' => _('company.com')
			);
		}
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusGroupName')) {
			$return['upload_columns'][] = array(
				'name' => 'freeRadius_radiusGroupName',
				'description' => _('Group names'),
				'help' => 'radiusGroupNameList',
				'example' => _('group01;group02')
			);
		}
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusFramedIPAddress')) {
			$return['upload_columns'][] = array(
				'name' => 'freeRadius_radiusFramedIPAddress',
				'description' => _('IP address'),
				'help' => 'radiusFramedIPAddress',
				'example' => '123.123.123.123',
			);
		}
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusFramedIPNetmask')) {
			$return['upload_columns'][] = array(
				'name' => 'freeRadius_radiusFramedIPNetmask',
				'description' => _('Net mask'),
				'help' => 'radiusFramedIPNetmask',
				'example' => '255.255.255.0'
			);
		}
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusExpiration')) {
			$return['upload_columns'][] = array(
				'name' => 'freeRadius_radiusExpiration',
				'description' => _('Expiration date'),
				'help' => 'radiusExpiration',
				'example' => '17.07.2017 00:00'
			);
		}
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusIdleTimeout')) {
			$return['upload_columns'][] = array(
				'name' => 'freeRadius_radiusIdleTimeout',
				'description' => _('Idle timeout'),
				'help' => 'radiusIdleTimeout',
				'example' => '3600'
			);
		}
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideDialupAccess')) {
			$return['upload_columns'][] = array(
				'name' => 'freeRadius_dialupAccess',
				'description' => _('Enabled'),
				'help' => 'dialupAccess',
				'example' => 'true',
				'values' => 'true, false'
			);
		}
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusProfileDn')) {
			$return['upload_columns'][] = array(
				'name' => 'freeRadius_radiusProfileDn',
				'description' => _('Profile'),
				'help' => 'radiusProfileDn',
				'example' => 'cn=profile,ou=radiusProfile,dc=example,dc=com'
			);
		}
		// available PDF fields
		$return['PDF_fields'] = array();
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusFramedIPAddress')) {
			$return['PDF_fields']['radiusFramedIPAddress'] = _('IP address');
		}
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusFramedIPNetmask')) {
			$return['PDF_fields']['radiusFramedIPNetmask'] = _('Net mask');
		}
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusRealm')) {
			$return['PDF_fields']['radiusRealm'] = _('Realm');
		}
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusGroupName')) {
			$return['PDF_fields']['radiusGroupName'] = _('Group names');
		}
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusExpiration')) {
			$return['PDF_fields']['radiusExpiration'] = _('Expiration date');
		}
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusIdleTimeout')) {
			$return['PDF_fields']['radiusIdleTimeout'] = _('Idle timeout');
		}
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideDialupAccess')) {
			$return['PDF_fields']['dialupAccess'] = _('Enabled');
		}
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusProfileDn')) {
			$return['PDF_fields']['radiusProfileDn'] = _('Profile');
		}
		return $return;
	}

	/**
	* Returns a list of configuration options.
	*
	* Calling this method does not require the existence of an enclosing {@link accountContainer}.<br>
	* <br>
	* The field names are used as keywords to load and save settings.
	* We recommend to use the module name as prefix for them (e.g. posixAccount_homeDirectory) to avoid naming conflicts.
	*
	* @param array $scopes account types (user, group, host)
	* @param array $allScopes list of all active account modules and their scopes (module => array(scopes))
	* @return mixed htmlElement or array of htmlElement
	*
	* @see htmlElement
	*/
	public function get_configOptions($scopes, $allScopes) {
		$configContainer = new htmlResponsiveRow();
		$configContainer->add(new htmlResponsiveInputField(_('Profile DN'), 'freeRadius_profileDN', '', 'profileDN'), 12);
		$configContainer->addVerticalSpacer('1rem');
		$hiddenGroup = new htmlGroup();
		$hiddenGroup->addElement(new htmlOutputText(_('Hidden options')));
		$hiddenGroup->addElement(new htmlHelpLink('hiddenOptions'));
		$configContainer->add($hiddenGroup, 12);
		$configContainer->addVerticalSpacer('0.5rem');
		$configContainer->add(new htmlResponsiveInputCheckbox('freeRadius_hideRadiusFramedIPAddress', false, _('IP address'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('freeRadius_hideRadiusFramedIPNetmask', false, _('Net mask'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('freeRadius_hideRadiusRealm', false, _('Realm'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('freeRadius_hideRadiusGroupName', false, _('Group names'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('freeRadius_hideRadiusExpiration', false, _('Expiration date'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('freeRadius_hideRadiusIdleTimeout', false, _('Idle timeout'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('freeRadius_hideRadiusProfileDn', false, _('Profile'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('freeRadius_hideDialupAccess', false, _('Enabled'), null, true), 12, 4);
		return $configContainer;
	}

	/**
	* This function fills the error message array with messages
	*/
	function load_Messages() {
		$this->messages['radiusFramedIPAddress'][0] = array('ERROR', _('The IP address is invalid.'));
		$this->messages['radiusFramedIPAddress'][1] = array('ERROR', _('Account %s:') . ' freeRadius_radiusFramedIPAddress', _('The IP address is invalid.'));
		$this->messages['radiusFramedIPNetmask'][0] = array('ERROR', _('The net mask is invalid.'));
		$this->messages['radiusFramedIPNetmask'][1] = array('ERROR', _('Account %s:') . ' freeRadius_radiusFramedIPNetmask', _('The net mask is invalid.'));
		$this->messages['radiusRealm'][0] = array('ERROR', _('Please enter a valid realm.'));
		$this->messages['radiusRealm'][1] = array('ERROR', _('Account %s:') . ' freeRadius_radiusRealm', _('Please enter a valid realm.'));
		$this->messages['radiusGroupName'][0] = array('ERROR', _('Please enter a valid list of group names.'));
		$this->messages['radiusGroupName'][1] = array('ERROR', _('Account %s:') . ' freeRadius_radiusGroupName', _('Please enter a valid list of group names.'));
		$this->messages['radiusExpiration'][0] = array('ERROR', _('The expiration date must be in format DD.MM.YYYY HH:MM.'));
		$this->messages['radiusExpiration'][1] = array('ERROR', _('Account %s:') . ' freeRadius_radiusExpiration', _('The expiration date must be in format DD.MM.YYYY HH:MM.'));
		$this->messages['radiusIdleTimeout'][0] = array('ERROR', _('Please enter a numeric value for the idle timeout.'));
		$this->messages['radiusIdleTimeout'][1] = array('ERROR', _('Account %s:') . ' freeRadius_radiusIdleTimeout', _('Please enter a numeric value for the idle timeout.'));
		$this->messages['dialupAccess'][0] = array('ERROR', _('Account %s:') . ' freeRadius_dialupAccess', _('This value can only be "true" or "false".'));
		$this->messages['radiusProfileDn'][0] = array('ERROR', _('Account %s:') . ' freeRadius_radiusProfileDn', _('This is not a valid DN!'));
	}

	/**
	 * Returns the HTML meta data for the main account page.
	 *
	 * @return htmlElement HTML meta data
	 */
	function display_html_attributes() {
		$return = new htmlResponsiveRow();
		if (in_array('radiusprofile', $this->attributes['objectClass'])) {
			// realm
			if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusRealm')) {
				$this->addSimpleInputTextField($return, 'radiusRealm', _('Realm'));
			}
			// group names
			if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusGroupName')) {
				$this->addMultiValueInputTextField($return, 'radiusGroupName', _('Group names'));
			}
			// IP address
			if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusFramedIPAddress')) {
				$this->addSimpleInputTextField($return, 'radiusFramedIPAddress', _('IP address'));
			}
			// net mask
			if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusFramedIPNetmask')) {
				$this->addSimpleInputTextField($return, 'radiusFramedIPNetmask', _('Net mask'));
			}
			// idle timeout
			if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusIdleTimeout')) {
				$radiusIdleTimeoutInput = $this->addSimpleInputTextField($return, 'radiusIdleTimeout', _('Idle timeout'));
				$radiusIdleTimeoutInput->setValidationRule(htmlElement::VALIDATE_NUMERIC);
			}
			// expiration date
			if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusExpiration')) {
				$radiusExpiration = '&nbsp;&nbsp;-';
				if (isset($this->attributes['radiusExpiration'][0])) {
					$radiusExpiration = $this->formatExpirationDate($this->attributes['radiusExpiration'][0]);
				}
				$return->addLabel(new htmlOutputText('Expiration date'));
				$radiusExpirationList = new htmlGroup();
				$radiusExpirationList->addElement(new htmlOutputText($radiusExpiration . ' &nbsp;&nbsp;&nbsp;&nbsp;', false));
				$radiusExpirationList->addElement(new htmlAccountPageButton(get_class($this), 'expiration', 'change', _('Change')));
				$radiusExpirationList->addElement(new htmlHelpLink('radiusExpiration'));
				$return->addField($radiusExpirationList);
			}
			// profile DN
			if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusProfileDn')) {
				$profiles = array('-' => '-');
				foreach ($this->getProfiles() as $dn) {
					$profiles[getAbstractDN($dn)] = $dn;
				}
				$profile = array();
				if (!empty($this->attributes['radiusProfileDn'][0])) {
					$profile = $this->attributes['radiusProfileDn'];
					if (!in_array($this->attributes['radiusProfileDn'][0], $profiles)) {
						$profiles[getAbstractDN($this->attributes['radiusProfileDn'][0])] = $this->attributes['radiusProfileDn'][0];
					}
				}
				$profileSelect = new htmlResponsiveSelect('radiusProfileDn', $profiles, $profile, _('Profile'), 'radiusProfileDn');
				$profileSelect->setHasDescriptiveElements(true);
				$return->add($profileSelect, 12);
			}
			// enabled
			if (!$this->isBooleanConfigOptionSet('freeRadius_hideDialupAccess')) {
				$enabled = array('');
				if (!empty($this->attributes['dialupAccess'][0])) {
					$enabled = array($this->attributes['dialupAccess'][0]);
					// value in LDAP may be anything other than "false" to count as "true"
					if (!in_array($this->attributes['dialupAccess'][0], array('true', 'false', 'TRUE', 'FALSE'))) {
						$enabled = array('true');
					}
				}
				$enabledOptions = array('-' => '', _('Yes') => 'true', _('No') => 'false');
				$enabledSelect = new htmlResponsiveSelect('dialupAccess', $enabledOptions, $enabled, _('Enabled'), 'dialupAccess');
				$enabledSelect->setHasDescriptiveElements(true);
				$return->add($enabledSelect, 12);
			}
			// button to remove extension
			$return->addVerticalSpacer('2rem');
			$remButton = new htmlButton('remObjectClass', _('Remove FreeRadius extension'));
			$return->add($remButton, 12, 12, 12, 'text-center');
		}
		else {
			$return->add(new htmlButton('addObjectClass', _('Add FreeRadius extension')), 12);
		}
		return $return;
	}

	/**
	* Processes user input of the primary module page.
	* It checks if all input values are correct and updates the associated LDAP attributes.
	*
	* @return array list of info/error messages
	*/
	function process_attributes() {
		if (isset($_POST['addObjectClass'])) {
			$this->attributes['objectClass'][] = 'radiusprofile';
			return array();
		}
		elseif (isset($_POST['remObjectClass'])) {
			$this->attributes['objectClass'] = array_delete(array('radiusprofile'), $this->attributes['objectClass']);
			for ($i = 0; $i < sizeof($this->meta['attributes']); $i++) {
				if (isset($this->attributes[$this->meta['attributes'][$i]])) {
					unset($this->attributes[$this->meta['attributes'][$i]]);
				}
			}
			return array();
		}
		// skip processing if extension is not active
		if (!in_array('radiusprofile', $this->attributes['objectClass'])) {
			return array();
		}
		$errors = array();
		// IP address
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusFramedIPAddress')) {
			$this->attributes['radiusFramedIPAddress'][0] = $_POST['radiusFramedIPAddress'];
			if (($_POST['radiusFramedIPAddress'] != '') && !get_preg($_POST['radiusFramedIPAddress'], 'ip')) {
				$errors[] = $this->messages['radiusFramedIPAddress'][0];
			}
		}
		// net mask
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusFramedIPNetmask')) {
			$this->attributes['radiusFramedIPNetmask'][0] = $_POST['radiusFramedIPNetmask'];
			if (($_POST['radiusFramedIPNetmask'] != '') && !get_preg($_POST['radiusFramedIPNetmask'], 'ip')) {
				$errors[] = $this->messages['radiusFramedIPNetmask'][0];
			}
		}
		// realm
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusRealm')) {
			$this->attributes['radiusRealm'][0] = $_POST['radiusRealm'];
			if (($_POST['radiusRealm'] != '') && !get_preg($_POST['radiusRealm'], 'DNSname')) {
				$errors[] = $this->messages['radiusRealm'][0];
			}
		}
		// group names
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusGroupName')) {
			$this->processMultiValueInputTextField('radiusGroupName', $errors, 'groupname');
		}
		// idle timeout
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusIdleTimeout')) {
			$this->attributes['radiusIdleTimeout'][0] = $_POST['radiusIdleTimeout'];
			if (($_POST['radiusIdleTimeout'] != '') && !get_preg($_POST['radiusIdleTimeout'], 'digit')) {
				$errors[] = $this->messages['radiusIdleTimeout'][0];
			}
		}
		// enabled
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideDialupAccess')) {
			if (!empty($this->attributes['dialupAccess']) && ($_POST['dialupAccess'] === '')) {
				unset($this->attributes['dialupAccess']);
			}
			elseif ($_POST['dialupAccess'] === 'false') {
				$this->attributes['dialupAccess'][0] = 'false';
			}
			elseif ($_POST['dialupAccess'] === 'true') {
				$this->attributes['dialupAccess'][0] = 'true';
			}
		}
		// profile DN
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusProfileDn')) {
			if (($_POST['radiusProfileDn'] == '-') && !empty($this->attributes['radiusProfileDn'])) {
				unset($this->attributes['radiusProfileDn']);
			}
			elseif ($_POST['radiusProfileDn'] != '-') {
				$this->attributes['radiusProfileDn'][0] = $_POST['radiusProfileDn'];
			}
		}
		return $errors;
	}

	/**
	* This function will create the meta HTML code to show a page to change the expiration date.
	*
	* @return htmlElement meta HTML code
	*/
	function display_html_expiration() {
		$return = new htmlResponsiveRow();
		$attr = 'radiusExpiration';
		$text = _('Expiration date');
		$help = "radiusExpiration";
		$date = new DateTime('@' . (time() + 3600*24*365), new DateTimeZone('UTC'));
		$year = $date->format('Y');
		$month = $date->format('m');
		$month = self::$monthList[$month];
		$day = $date->format('d');
		$hour = '00';
		$minute = '00';
		if (isset($this->attributes[$attr][0]) && ($this->attributes[$attr][0] != '')) {
			$parts = explode(' ', $this->attributes[$attr][0]);
			$year = $parts[2];
			$month = $parts[1];
			$day = $parts[0];
			if (isset($parts[3])) {
				$timeParts = explode(':', $parts[3]);
				$hour = $timeParts[0];
				$minute = $timeParts[1];
			}
		}
		for ( $i=0; $i<=59; $i++ ) {
			$minuteList[] = str_pad($i, 2, '0', STR_PAD_LEFT);
		}
		for ( $i=0; $i<=23; $i++ ) {
			$hourList[] = str_pad($i, 2, '0', STR_PAD_LEFT);
		}
		for ( $i=1; $i<=31; $i++ ) {
			$dayList[] = str_pad($i, 2, '0', STR_PAD_LEFT);
		}
		for ( $i=2003; $i<=2050; $i++ ) {
			$yearList[] = $i;
		}
		$return->addLabel(new htmlOutputText($text));
		$dateGroup = new htmlGroup();
		$daySelect = new htmlSelect('expire_day', $dayList, array($day));
		$daySelect->setWidth('3rem');
		$dateGroup->addElement($daySelect);
		$monthSelect = new htmlSelect('expire_mon', self::$monthList, array($month));
		$monthSelect->setHasDescriptiveElements(true);
		$monthSelect->setWidth('3rem');
		$dateGroup->addElement($monthSelect);
		$yearSelect = new htmlSelect('expire_yea', $yearList, array($year));
		$yearSelect->setWidth('5rem');
		$dateGroup->addElement($yearSelect);
		$dateGroup->addElement(new htmlSpacer('10px', null));
		$hourSelect = new htmlSelect('expire_hour', $hourList, array($hour));
		$hourSelect->setWidth('3rem');
		$dateGroup->addElement($hourSelect);
		$minuteSelect = new htmlSelect('expire_minute', $minuteList, array($minute));
		$minuteSelect->setWidth('3rem');
		$dateGroup->addElement($minuteSelect);
		$dateGroup->addElement(new htmlHelpLink($help));
		$return->addField($dateGroup);
		$return->addVerticalSpacer('2rem');
		$buttons = new htmlGroup();
		$buttons->addElement(new htmlAccountPageButton(get_class($this), 'attributes', 'change' . $attr, _('Change')));
		$buttons->addElement(new htmlSpacer('0.5rem', null));
		if (isset($this->attributes[$attr][0])) {
			$buttons->addElement(new htmlAccountPageButton(get_class($this), 'attributes', 'del' . $attr, _('Remove')));
			$buttons->addElement(new htmlSpacer('0.5rem', null));
		}
		$buttons->addElement(new htmlAccountPageButton(get_class($this), 'attributes', 'back' . $attr, _('Cancel')));
		$return->add($buttons, 12);
		return $return;
	}

	/**
	* Processes user input of the time selection page.
	*
	* @return array list of info/error messages
	*/
	function process_expiration() {
		$return = array();
		// find button name
		$buttonName = '';
		$postKeys = array_keys($_POST);
		for ($i = 0; $i < sizeof($postKeys); $i++) {
			if (strpos($postKeys[$i], 'form_subpage_freeRadius_attributes_') !== false) {
				$buttonName = $postKeys[$i];
			}
		}
		if (($buttonName == '') || (strpos($buttonName, '_back') !== false)) return array();
		// get attribute name
		$attr = '';
		if (strpos($buttonName, 'radiusExpiration') !== false) {
			$attr = 'radiusExpiration';
		}
		if ($attr == '') return array();
		// determine action
		if (strpos($buttonName, '_change') !== false) {
			// set new expiration date
			$this->attributes[$attr][0] = $_POST['expire_day'] . ' ' . $_POST['expire_mon'] . ' ' . $_POST['expire_yea'] . ' ' . $_POST['expire_hour'] . ':' . $_POST['expire_minute'];
		}
		elseif (strpos($buttonName, '_del') !== false) {
			// remove attribute value
			unset($this->attributes[$attr]);
		}
		return $return;
	}

	/**
	* Returns a list of modifications which have to be made to the LDAP account.
	*
	* @return array list of modifications
	* <br>This function returns an array with 3 entries:
	* <br>array( DN1 ('add' => array($attr), 'remove' => array($attr), 'modify' => array($attr)), DN2 .... )
	* <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)
	* <br>"add" are attributes which have to be added to LDAP entry
	* <br>"remove" are attributes which have to be removed from LDAP entry
	* <br>"modify" are attributes which have to been modified in LDAP entry
	* <br>"info" are values with informational value (e.g. to be used later by pre/postModify actions)
	*/
	function save_attributes() {
		if (!in_array('radiusprofile', $this->attributes['objectClass']) && !in_array('radiusprofile', $this->orig['objectClass'])) {
			// skip saving if the extension was not added/modified
			return array();
		}
		return parent::save_attributes();
	}

	/**
	 * {@inheritDoc}
	 * @see baseModule::build_uploadAccounts()
	 */
	function build_uploadAccounts($rawAccounts, $ids, &$partialAccounts, $selectedModules, &$type) {
		$errors = array();
		for ($i = 0; $i < sizeof($rawAccounts); $i++) {
			// add object class
			if (!in_array("radiusprofile", $partialAccounts[$i]['objectClass'])) $partialAccounts[$i]['objectClass'][] = "radiusprofile";
			// IP address
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'freeRadius_radiusFramedIPAddress', 'radiusFramedIPAddress',
				'ip', $this->messages['radiusFramedIPAddress'][1], $errors);
			// net mask
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'freeRadius_radiusFramedIPNetmask', 'radiusFramedIPNetmask',
				'ip', $this->messages['radiusFramedIPNetmask'][1], $errors);
			// realm
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'freeRadius_radiusRealm', 'radiusRealm',
				'DNSname', $this->messages['radiusRealm'][1], $errors);
			// group names
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'freeRadius_radiusGroupName', 'radiusGroupName', 'groupname', $this->messages['radiusGroupName'][1], $errors, '/;[ ]*/');
			// expiration date
			if ($rawAccounts[$i][$ids['freeRadius_radiusExpiration']] != "") {
				if (preg_match('/^[0-9]{1,2}.[0-9]{1,2}.[0-9]{4} [0-9]{1,2}:[0-9]{1,2}$/', $rawAccounts[$i][$ids['freeRadius_radiusExpiration']])) {
					$dateParts = explode(' ', $rawAccounts[$i][$ids['freeRadius_radiusExpiration']]);
					$dateParts1 = explode('.', $dateParts[0]);
					$radiusExpiration = str_pad($dateParts1[0], 2, '0', STR_PAD_LEFT) . ' ' . self::$monthList[str_pad($dateParts1[1], 2, '0', STR_PAD_LEFT)] . ' ' . $dateParts1[2];
					$dateParts2 = explode(':', $dateParts[1]);
					$radiusExpiration .= ' ' . str_pad($dateParts2[0], 2, '0', STR_PAD_LEFT) . ':' . str_pad($dateParts2[1], 2, '0', STR_PAD_LEFT);
					$partialAccounts[$i]['radiusExpiration'] = $radiusExpiration;
				}
				else {
					$errMsg = $this->messages['radiusExpiration'][1];
					array_push($errMsg, array($i));
					$errors[] = $errMsg;
				}
			}
			// idle timeout
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'freeRadius_radiusIdleTimeout', 'radiusIdleTimeout',
				'digit', $this->messages['radiusIdleTimeout'][1], $errors);
			// enabled
			if (!empty($rawAccounts[$i][$ids['freeRadius_dialupAccess']])) {
				if (in_array($rawAccounts[$i][$ids['freeRadius_dialupAccess']], array('true', 'false'))) {
					$partialAccounts[$i]['dialupAccess'] = $rawAccounts[$i][$ids['freeRadius_dialupAccess']];
				}
				else {
					$errMsg = $this->messages['dialupAccess'][0];
					array_push($errMsg, array($i));
					$errors[] = $errMsg;
				}
			}
			// profile DN
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'freeRadius_radiusProfileDn', 'radiusProfileDn', 'dn', $this->messages['radiusProfileDn'][0], $errors);
		}
		return $errors;
	}

	/**
	 * {@inheritDoc}
	 * @see baseModule::get_pdfEntries()
	 */
	function get_pdfEntries($pdfKeys, $typeId) {
		$return = array();
		$this->addSimplePDFField($return, 'radiusFramedIPAddress', _('IP address'));
		$this->addSimplePDFField($return, 'radiusFramedIPNetmask', _('Net mask'));
		$this->addSimplePDFField($return, 'radiusRealm', _('Realm'));
		$this->addSimplePDFField($return, 'radiusGroupName', _('Group names'));
		$this->addSimplePDFField($return, 'radiusIdleTimeout', _('Idle timeout'));
		$this->addSimplePDFField($return, 'radiusProfileDn', _('Profile'));
		if (isset($this->attributes['radiusExpiration'][0])) {
			$this->addPDFKeyValue($return, 'radiusExpiration', _('Expiration date'), $this->formatExpirationDate($this->attributes['radiusExpiration'][0]));
		}
		if (isset($this->attributes['dialupAccess'][0])) {
			$enabled = _('Yes');
			if (in_array($this->attributes['dialupAccess'][0], array('false', 'FALSE'))) {
				$enabled = _('No');
			}
			$this->addPDFKeyValue($return, 'dialupAccess', _('Enabled'), $enabled);
		}
		return $return;
	}

	/**
	* {@inheritDoc}
	*/
	function check_profileOptions($options, $typeId) {
		$messages = parent::check_profileOptions($options, $typeId);
		// group names
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusGroupName')
			&& isset($options['freeRadius_radiusGroupName'][0])
			&& ($options['freeRadius_radiusGroupName'][0] != '')) {
			$list = preg_split('/;[ ]*/', $options['freeRadius_radiusGroupName'][0]);
			for ($i = 0; $i < sizeof($list); $i++) {
				if (!get_preg($list[$i], 'groupname')) {
					$messages[] = $this->messages['radiusGroupName'][0];
					break;
				}
			}
		}
		return $messages;
	}

	/**
	* Loads the values of an account profile into internal variables.
	*
	* @param array $profile hash array with profile values (identifier => value)
	*/
	function load_profile($profile) {
		// profile mappings in meta data
		parent::load_profile($profile);
		// group names
		if (!$this->isBooleanConfigOptionSet('freeRadius_hideRadiusGroupName')
			&& isset($profile['freeRadius_radiusGroupName'][0])
			&& ($profile['freeRadius_radiusGroupName'][0] != '')) {
			$this->attributes['radiusGroupName'] = preg_split('/;[ ]*/', $profile['freeRadius_radiusGroupName'][0]);
		}
	}

	/**
	 * Formats the expiration date attribute.
	 *
	 * @param String $date date value
	 */
	private function formatExpirationDate($date) {
		if (is_null($date) || ($date == '')) {
			return $date;
		}
		foreach (self::$monthList as $replace => $search) {
			$date = str_replace($search, $replace, $date);
		}
		$dateParts = explode(' ', $date);
		$date = $dateParts[0] . '.' . $dateParts[1] . '.' . $dateParts[2];
		if (isset($dateParts[3])) {
			$date .= ' ' . $dateParts[3];
		}
		return $date;
	}

	/**
	 * Returns a list of possible profile DNs.
	 *
	 * @return array list of profile DNs
	 */
	private function getProfiles() {
		if ($this->profileCache != null) {
			return $this->profileCache;
		}
		if (empty($this->moduleSettings['freeRadius_profileDN'][0])) {
			return array();
		}
		$list = searchLDAP($this->moduleSettings['freeRadius_profileDN'][0], '(objectClass=radiusProfile)', array('dn'));
		foreach ($list as $attr) {
			$this->profileCache[] = $attr['dn'];
		}
		usort($this->profileCache, 'compareDN');
		return $this->profileCache;
	}

	/**
	 * Returns a list of jobs that can be run.
	 *
	 * @param LAMConfig $config configuration
	 * @return array list of jobs
	 */
	public function getSupportedJobs(&$config) {
		return array(
			new FreeRadiusAccountExpirationCleanupJob(),
			new FreeRadiusAccountExpirationNotifyJob()
		);
	}

}

if (interface_exists('\LAM\JOB\Job', false)) {

	include_once dirname(__FILE__) . '/../passwordExpirationJob.inc';

	/**
	 * Job to delete or move users on account expiration.
	 *
	 * @package jobs
	 */
	class FreeRadiusAccountExpirationCleanupJob extends \LAM\JOB\AccountExpirationCleanupJob {

		/**
		 * Returns the alias name of the job.
		 *
		 * @return String name
		 */
		public function getAlias() {
			return _('FreeRadius') .  ': ' . _('Cleanup expired user accounts');
		}

		/**
		 * Returns the description of the job.
		 *
		 * @return String description
		 */
		public function getDescription() {
			return _('This job deletes or moves user accounts when they expire.');
		}

		/**
		 * Searches for users in LDAP.
		 *
		 * @param String $jobID unique job identifier
		 * @param array $options config options (name => value)
		 * @return array list of user attributes
		 */
		protected function findUsers($jobID, $options) {
			// read users
			$attrs = array('radiusExpiration');
			return searchLDAPByFilter('(radiusExpiration=*)', $attrs, array('user'));
		}

		/**
		 * Checks if a user is expired.
		 *
		 * @param integer $jobID job ID
		 * @param array $options job settings
		 * @param PDO $pdo PDO
		 * @param DateTime $now current time
		 * @param array $policyOptions list of policy options by getPolicyOptions()
		 * @param array $user user attributes
		 * @param boolean $isDryRun just do a dry run, nothing is modified
		 */
		protected function checkSingleUser($jobID, $options, &$pdo, $now, $policyOptions, $user, $isDryRun) {
			$expireTime = DateTime::createFromFormat('d M Y H:i', $user['radiusexpiration'][0], new DateTimeZone('UTC'));
			$this->jobResultLog->logDebug("Expiration on " . $expireTime->format('Y-m-d'));
			$delay = 0;
			if (!empty($options[$this->getConfigPrefix() . '_delay' . $jobID][0])) {
				$delay = $options[$this->getConfigPrefix() . '_delay' . $jobID][0];
			}
			$actionTime = clone $expireTime;
			if ($delay != 0) {
				$actionTime->add(new DateInterval('P' . $delay . 'D'));
			}
			$actionTime->setTimeZone(getTimeZone());
			$this->jobResultLog->logDebug("Action time on " . $actionTime->format('Y-m-d'));
			if ($actionTime <= $now) {
				$this->performAction($jobID, $options, $user, $isDryRun);
			}
		}

	}

	/**
	 * Job to notify users about account expiration.
	 *
	 * @package jobs
	 */
	class FreeRadiusAccountExpirationNotifyJob extends \LAM\JOB\PasswordExpirationJob {

		/**
		 * {@inheritDoc}
		 * @see \LAM\JOB\Job::getAlias()
		 */
		public function getAlias() {
			return _('FreeRadius') . ': ' . _('Notify users about account expiration');
		}

		/**
		 * {@inheritDoc}
		 * @see \LAM\JOB\PasswordExpirationJob::getDescription()
		 */
		public function getDescription() {
			return _('This job sends out emails to inform your users that their account will expire soon.');
		}

		/**
		 * {@inheritDoc}
		 * @see \LAM\JOB\PasswordExpirationJob::findUsers()
		 */
		protected function findUsers($jobID, $options) {
			// read users
			$sysattrs = array('radiusExpiration', 'mail');
			$attrs = $this->getAttrWildcards($jobID, $options);
			$attrs = array_values(array_unique(array_merge($attrs, $sysattrs)));
			return searchLDAPByFilter('(&(radiusExpiration=*)(mail=*))', $attrs, array('user'));
		}

		/**
		 * {@inheritDoc}
		 * @see \LAM\JOB\PasswordExpirationJob::checkSingleUser()
		 */
		protected function checkSingleUser($jobID, $options, &$pdo, $now, $policyOptions, $user, $isDryRun) {
			$dn = $user['dn'];
			// get time when account expires
			$expirationTime = DateTime::createFromFormat('d M Y H:i', $user['radiusexpiration'][0], new DateTimeZone('UTC'));
			$this->jobResultLog->logDebug("Account expiration on " . $expirationTime->format('Y-m-d'));
			// skip if account itself is expired
			if ($expirationTime <= $now) {
				$this->jobResultLog->logDebug($dn . ' already expired');
				return;
			}
			$numDaysToWarn = $options[$this->getConfigPrefix() . '_mailNotificationPeriod' . $jobID][0];
			$this->jobResultLog->logDebug("Number of days before warning " . $numDaysToWarn);
			// calculate time of notification
			$notifyTime = clone $expirationTime;
			$notifyTime->sub(new DateInterval('P' . $numDaysToWarn . 'D'));
			$notifyTime->setTimeZone(getTimeZone());
			$this->jobResultLog->logDebug("Account expiration notification on " . $notifyTime->format('Y-m-d H:i'));
			// skip if notification is in the future
			if ($notifyTime > $now) {
				$this->jobResultLog->logDebug($dn . ' does not need notification yet.');
				return;
			}
			$dbLastChange = $this->getDBLastPwdChangeTime($jobID, $pdo, $dn);
			// skip entries where mail was already sent
			if ($dbLastChange == $user['radiusexpiration'][0]) {
				$this->jobResultLog->logDebug($dn . ' was already notified.');
				return;
			}
			if ($isDryRun) {
				// no action for dry run
				$this->jobResultLog->logInfo('Not sending email to ' . $dn . ' because of dry run.');
				return;
			}
			// send email
			$success = $this->sendMail($options, $jobID, $user, $expirationTime);
			// update DB if mail was sent successfully
			if ($success) {
				$this->setDBLastPwdChangeTime($jobID, $pdo, $dn, $user['radiusexpiration'][0]);
			}
		}

	}

}


?>
