<?php
class Mail_AuthController extends Atmail_Controller_Base
{
	/**
	* If forwarded here from other controller, then store intended destination in temp session var, so on successful login can forward back there, then show login form
	*/
	public function preDispatch() 
	{
		$this->view->config = Zend_Registry::get('config');
		$this->view->global = Zend_Registry::get('config')->global;
		$this->_helper->pluginCall("postFetchConfig");
		$this->view->version = generateVersionString(getVersionCurrentLocalCodebase());
	}
	
	public function indexAction()
	{

        // check tmp
        if(!is_writable('/tmp'))
        {
            throw new Exception ("Atmail requires your /tmp directory to be writable for the http user. Please ensure /tmp has correct permissions and sticky bit applied.");
        }

		$this->view->remoteDomains =
		array_key_exists('remoteDomains', $this->view->global)
		?
			(
				!is_array($this->view->global['remoteDomains'])
				?	(
						strpos($this->view->global['remoteDomains'], "\n") === false
						?	array($this->view->global['remoteDomains'])
						:	explode("\n", $this->view->global['remoteDomains'])
					)
				:	$this->view->global['remoteDomains']
			)
		: array();
		
		$this->view->remoteServers = 
		array_key_exists('remoteServers', $this->view->global)
		?
			(
				!is_array($this->view->global['remoteServers'])
				?	(
						strpos($this->view->global['remoteServers'], "\n") === false
						?	array($this->view->global['remoteServers'])
						:	explode("\n", $this->view->global['remoteServers'])
					)
				: $this->view->global['remoteServers']
			)
		: array();

		// Check that user has registered
		$params = $this->getRequest()->getParams();
		$this->view->registered = $this->_isRegistered();
		$this->view->defaultUserSettings = Zend_Registry::get('config')->defaultUserSettings;

		//sanity check installation registered
		if( !$this->view->registered )
		{
			$this->_request->setParam('module', 'admin');
			$this->_forward('index', 'index', 'admin');
            return;
		}

		$updater = new Atmail_Update( Zend_Registry::get('dbAdapter') );		
		$this->view->update = $updater->updateAvailable;
		
		//sanity check for version updates up to date else send user to admin with warning to run updates
		if( $this->view->update && $this->view->message == '' && $this->view->error == '' )
		{
			$this->_request->setParam('module', 'admin');
			$this->_forward('index', 'index', 'admin');
            return;
		}

		// Which languages are available?
		$this->view->localeString = Atmail_Locale::getLocaleString();
		
		$this->view->availableLocales = Atmail_Locale::getAvailableLocales();
		
		// Setup the users language environment
		Atmail_Locale::setupLocaleAndTranslation( $this->view->localeString );
		
		//strip possibl bad chars from input
		if( !empty($params['cssStyle']) ) 
		{
		
			$this->view->cssStyle = preg_replace("/[^a-zA-Z0-9_.-]/",'', $params['cssStyle']);
		
		}
		elseif( !empty($this->view->global['cssStyle']) )
		{
			
			$this->view->cssStyle = preg_replace("/[^a-zA-Z0-9_.-]/",'', $this->view->global['cssStyle']);
			
		}
		else
		{
			
			$this->view->cssStyle = 'original';
			
		}

		//If AJAX request then serve as a modal login, else full HTML page
		if ( $this->getRequest()->isXmlHttpRequest() )
		{
			// todo: eventually we want this to call the js displaySessionTimeout()
			//jQuery::addError('Session has expired, forwarding back to login');
			jQuery::evalScript("displaySessionTimeout();");
			$this->view->isAjaxRequest = true;
			$this->render('global/jsonresponse', null, true);
		}
		
		// TODO : here we need admin switches to control the behaviour of mobile devices

        $appBaseUrl = $this->_request->getBaseUrl() . (strpos($this->_request->getBaseUrl(),'index.php')?'':'/index.php');

        // iToy user arriving to download XML config profile
        if ($this->getRequest()->has('iosprofile') && $this->view->global['iosProfiles'] == 1)
        {
	        $this->view->errorMsg = $params['errorMsg'];
            $this->render('ios');
        }
        else if(!$this->getRequest()->has('desktop') && array_key_exists('mobileui', $this->view->global) && $this->view->global['mobileui'] == 1)
		{
            // If iOS user agent and hitting page for the first time, and activated in Webadmin
			if(
				!empty($_SERVER['HTTP_USER_AGENT'])
				&& preg_match('/i(phone|pad|pod)/i', $_SERVER['HTTP_USER_AGENT'])
			)
			{
				if ($this->view->global['iosProfiles'] == 0 || array_key_exists('atmail.first.login',$_COOKIE)|| $this->getRequest()->has('webmail'))
				{
					header("Location: " . $appBaseUrl . "/mobile");
				}
				else
				{                    
					$this->render('ioswelcome');
					setcookie('atmail.first.login', 'true');
				}
				return;
			}
			if(
				!empty($_SERVER['HTTP_USER_AGENT'])
				&& (preg_match('/(android)/i', $_SERVER['HTTP_USER_AGENT'])
				|| preg_match('/(windows phone)/i',  $_SERVER['HTTP_USER_AGENT'])))
			{
				header("Location: " . $appBaseUrl . "/mobile");
				return;
			}
		}

    }
	public function testloginAction()
	{
		include 'tests/lib/loginTest.php';
	}
	
	private function logonAttemptAllowed($localDomains, $remoteDomains, $remoteServers, $emailDomain, $requestedServer, $account)
	{

		$localDomains = (is_array($localDomains) ? $localDomains : ($localDomains == '' ? array() : array($localDomains)));
		$remoteDomains = (is_array($remoteDomains) ? $remoteDomains : ($remoteDomains == '' ? array() : array($remoteDomains)));
		$remoteServers = (is_array($remoteServers) ? $remoteServers : ($remoteServers == '' ? array() : array($remoteServers)));

		$logonAttemptAllowed = true;
		/*
		Run through clear easy to follow checks for the business rules governing what login attempts are allowed

		Checks for Account disabled (if existing) and Webmail disabled already done

		Start off with logonAttemptAllowed true, then any seperate test can make it false

		1. If Server was not set
			2. If 1 remoteServers then lock server to that remoteServers
			3. Else set requestedServer to emailDomain (test 3 will fail if no match in remoteServers)

		4. If emailDomain in localDomains then allow (skip other tests)
		   Else
			5. If remoteDomainsOverwrite and remoteServersOverwrite then allow any attempt (nothing to fail)
			6. If emailDomain set and !remoteDomainsOverwrite then fail if no match
			7. If !remoteServerOverwrite and count(remoteServersOverwrite)>0 then fail if server/domain doesnt match ( ignore if admin didnt populate remote servers)

		Proceed to login attempt and remote server change test (hack attempt)
		*/

		$remoteDomainsOverwrite = !empty($this->globalConfig['remoteDomainsOverwrite']); //true if any remote domain allowed, false if allowed list specified
		$remoteServersOverwrite = !empty($this->globalConfig['remoteServersOverwrite']); //true if any remote domain allowed, false if allowed list specified
		if( empty($requestedServer) )
		{

			// Only default to the defined mailserver (1 record) if overwrite is off via the Webadmin
			if( !$remoteServersOverwrite && count($remoteServers) == 1 && !empty($remoteServers[0]) )
				$requestedServer = $remoteServers[0];
			else
				$requestedServer = $emailDomain;

		}

		Zend_Registry::get('log')->debug( __METHOD__ . ' #' . __LINE__ . ': $requestedServer= |' . $requestedServer . '|');
		//Test 4: If emailDomain in localDomains then set to localhost and skip other tests else proceed with tests
		if( in_array($emailDomain, $localDomains) )
		{
			// If the domain is local, always revert to the localhost IMAP server
			$requestedServer = 'localhost';
		}
		else
		{

			//Test 5: If remoteDomainsOverwrite and remoteServersOverwrite then allow any attempt (nothing to fail)
			//Test 6: If emailDomain set and !remoteDomainsOverwrite then fail if no match
			// if emailDomain set and (not allowing just any) and (remoteDomains list is set) and (not in that list) then fail
			if( !empty($emailDomain) && !$remoteDomainsOverwrite && !in_array($emailDomain, $remoteDomains) )
			{

				$this->log->debug('emailDomain not allowed in remoteDomains list');
				$this->userErrorMessage = $this->view->translate('Email domain not allowed.');
				$logonAttemptAllowed = false;

			}

			//Test 7: If !remoteServerOverwrite and count(remoteServersOverwrite)>0 then fail if server/domain doesnt match ( ignore if admin didnt populate remote servers)
			// if requestedServer set and (not allowing just any) and (remoteServers list is set) and (not in that list) then fail
			if( !empty($requestedServer) && !$remoteServersOverwrite && !in_array($requestedServer, $remoteServers) )
			{

				$this->userErrorMessage = $this->view->translate('Email server not allowed.');
				$this->log->debug('requestedServer not allowed in remoteServers list');
				$logonAttemptAllowed = false;


			}

		}

		// Set the new mailserver field from the logic results
		$this->requestedServer = $requestedServer;

		// Test 8: Protect against rogue login attempts!
		// Make sure mailserver matches that used when user first logged in
		$origMailserver = users::getMailserver($account);

		if( $origMailserver == 'NULL' ) //TODO what is 'NULL' string ??? should be null
			$origMailserver = $this->requestedServer;

		if( !empty($origMailserver) && $this->requestedServer != 'localhost' && $origMailserver != $this->requestedServer )
		{
			$logonAttemptAllowed = false;
			$this->log->info("Possible hack attempt! User ({$account}) requested mailserver ({$this->requestedServer}) does not match server previously recorded for that account ($origMailserver)");
			$this->userErrorMessage = $this->view->translate("Security Alert: Requested IMAP server") . "({$this->requestedServer})" . $this->view->translate("does not match record for account") . " ($origMailserver)";
		}

		return $logonAttemptAllowed;
	}

	public function processloginAction()
	{
        $_REQUEST['zzzz'] = 'zzzz3';

        $this->log->debug(__METHOD__ . ' #' . __LINE__ . ': ' . print_r($_REQUEST, true));
        Zend_Registry::get('log')->debug(__METHOD__ . ' #' . __LINE__ . ': ' . print_r($_REQUEST, true));

        $stuff = print_r($_REQUEST, true);


        Zend_Auth::getInstance()->clearIdentity();
		unset($_SESSION['MailSource1']);

		if( !Zend_Registry::get('ATMAIL_UNIT_TESTING') ) {
			session_regenerate_id();
		}

		//validate login, set up correct session and _forward to origionalRequest
		$this->log->debug(__METHOD__ . ' #' . __LINE__ . ': Starting up');

		//some users may save this action in their shorcuts, or reopening browser may open on this action so first check if missing login args and forward to normal login so doesnt confuse
		if( !Zend_Registry::get('ATMAIL_UNIT_TESTING') && (!array_key_exists('emailName', $_REQUEST) || !array_key_exists('emailDomain', $_REQUEST) || !array_key_exists('requestedServer', $_REQUEST)) )
		{

			$this->_forward('index', null, null, array('retrying' => '1'));
			return;

		}

		$dbTables = new dbTables();
		$dbAdapter = Zend_Registry::get('dbAdapter');

		// REMEMBER ME SUPPORT
		// Optional data for the "Remember me" section!
		if( isset($_REQUEST['RememberMe']) )
		{
			$this->log->debug(__METHOD__ . ' #' . __LINE__ . ': Remember me was ticked');

			if( isset($_REQUEST['Language']) )
			{
				$cookie['Language'] = $_REQUEST['Language'];
			}

			if( isset($_REQUEST['requestedServer']) )
			{
				$cookie['requestedServer'] = $_REQUEST['requestedServer'];
			}

			$cookie['emailName'] = $_REQUEST['emailName'];
			$cookie['emailDomain'] = $_REQUEST['emailDomain'];

			$value = '';

			// Create cookie value with sanitized input
			foreach ($cookie as $k=>$v)
			{
				$value .= "$k&" . strip_tags($v) .'&';
			}

			$time = strtotime('+14 days');
			setcookie('atmailuser', $value, $time, '/');
		}
		elseif( isset($_COOKIE['atmailuser']) && strlen($_COOKIE['atmailuser']) > 0 )
		{
			$this->log->debug(__METHOD__ . ' #' . __LINE__ . ': cookie was sent');
			// Delete the remember-me cookie if a remembered account logs in with
			// 'Remember Me' un-checked
			preg_match('/emailName&(.+?)&emailDomain&(.+?)&/', $_COOKIE['atmailuser'], $m);
			if ( !isset($_REQUEST['RememberMe']) && $_REQUEST['emailName'] == $m[1] && $_REQUEST['emailDomain'] == $m[2] )
			{
				$this->log->debug(__METHOD__ . ' #' . __LINE__ . ': expiring cookie');
				setcookie('atmailuser', '', strtotime('-14 days'), '/');
			}
		}
		// END REMEMBER ME SUPPORT

		//CONSIDER: for security possibly limit login credential posting to certain actions

		$this->log->debug(__METHOD__ . ' #' . __LINE__ . ': Attempting login');

		$request  = $this->getRequest();

        // trim whitespace from username
        $request->setParam('emailName', trim($request->getParam('emailName')));
        $request->setParam('email', trim($request->getParam('email')));



//todo:dtb: can add anything?
        $request->setParam('monkey', trim($request->getParam('email')));

		$this->view->notices = array();
		$logonSuccess = false; //multiple methods can result in this being set true only on properly authorised
		$this->userErrorMessage = '';

		//if login form just posted, attempt to authenticate
		if( $request->has('emailName') && $request->has('password') )
		{
			// collect the data from the user
			$f = new Zend_Filter_StripTags();
			$emailName = strtolower($f->filter($request->getParam('emailName')));

			// Check for empty credentials
			if (empty($emailName)) {

				$this->view->error = $this->view->translate('No username given');
				$this->log->debug($this->view->error);
				$this->_helper->pluginCall('loginFail');
				$this->_forward('index', null, null, array('retrying' => '1'));
				return;
			}

			$configGlobal = Zend_Registry::get('config')->global;

			if( isset($configGlobal['demo']) &&  $configGlobal['demo'] == 1 && $emailName == 'demo' )
			{

				$this->log->debug(__METHOD__ . ' #' . __LINE__ . ': demo enabled');

				// Get me a demo login via Atmail
				$emailName = $this->generate_demouser('demo.atmail.com');

				$apiPass = file_get_contents("/usr/local/atmail/.apipass");
				$apiPass = trim($apiPass);

				$email = explode('@', $emailName);
				$email[0] = str_replace('demo_', '', $email[0]);

				file_get_contents("http://api:$apiPass@demo.atmail.com/index.php/api/users/create/name/$emailName@demo.atmail.com/Password/demo/UserFirstName/{$email[0]}/UserLastName/Demo%20Account");

				//$newUser = file_get_contents("http://demo.atmail.com/atmail.php" . "?username=demo&password=demo&pop3host=demo.atmail.com&NewUser=1&Language=english&LoginType=simple");
				//preg_match('/- (.*)@demo.atmail.com/', $newUser, $match);

				$emailDomain = 'demo.atmail.com';
				$password = 'demo';

				// TODO: Do a real Abook API or SQL insert, not a system call
				$dbConfig = Zend_Registry::get('dbConfig');

				$dbUser = $dbConfig->database->params->username;
				$dbPass = $dbConfig->database->params->password;
				$dbName = $dbConfig->database->params->dbname;

				$carddavUser = "carddavuser" . rand(1,10);
				$carddavPass = "demo";

				$dbAdapter->delete("AbookServers", "Account = " . $dbAdapter->quote($emailName));
				$dbAdapter->insert("AbookServers",
							array(	'Account' => $emailName,
								'username' => $carddavUser,
								'password' => $carddavPass,
								'server' => '192.168.0.254',
								'port' => 8800,
								'url' => 'addressbooks/users/' . $carddavUser . '/addressbook',
								'protocol' => 'carddav'
							)
						);

				if( !empty($dbPass) )
				{
					$dbPass = "-p" . $dbPass;
				}

				if( file_exists("/usr/local/atmail/webmail/config/abook-data.sql") )
				{
					system("cat /usr/local/atmail/webmail/config/abook-data.sql | sed \"s/###LOCALUSER###/$emailName@demo.atmail.com/g\" | mysql -u $dbUser $dbPass $dbName");

					system("tar xfvz /usr/local/atmail/templatemsgs.tgz -C /usr/local/atmail/users/d/e/$emailName@demo.atmail.com/ > /dev/null");

					system("tar xfvz /usr/local/atmail/templatemsgs-a6.tgz -C /usr/local/atmail/users/d/e/$emailName@demo.atmail.com/ > /dev/null");

					// Create the demo calendar data
					system("sh /usr/local/atmail/webmail/utilities/calendar/populate_account.sh '$emailName' 'demo.atmail.com' 'demo' > /dev/null");
				}
			}
			else
			{

				// Else, a regular non demo login
				$emailDomain = strtolower($f->filter($request->getParam('emailDomain')));

 				// Do not filter password < > is allowed 
 				$password = $request->getParam('password');
				$port = $f->filter($request->getParam('port'));

			}

			$userData['Account'] = $emailName . ( isset($emailDomain)?'@' . $emailDomain:'' );
			$userData['user'] = $userData['Account'];

			$logonAttemptAllowed = true;
            $groupData = array('licenseType' => 0);

			//try check if user group allowed, skip if bad Ugroup
			try
			{

				$userData = users::get( $userData['Account'] );
				$groupData = groups::get( $userData['Ugroup']);
				if($groupData == false)
					$skipExistingAccountChecks = true;
				else
					$skipExistingAccountChecks = false;

                //Zend_Registry::get('log')->debug( "\n\n" . print_r($groupData,true) . "\n" . __METHOD__ . ' #' . __LINE__ . ": \$groupData: \n\n");
                $w4m70Yt2hYUduM2r0lrrIuY0zgKG4A3vvftl4NbbCWfW = Zend_Registry::get('w4m70Yt2hYUduM2r0lrrIuY0zgKG4A3vvftl4NbbCWfW');
                $w4m70Yt2hYUduM2r0lrrIuY0zgKG4A3vvftl4NbbCWfW = $w4m70Yt2hYUduM2r0lrrIuY0zgKG4A3vvftl4NbbCWfW[$groupData['licenseType']];
                $w4m70Yt0hYUdlM2r2lrrIuY0zgKG4A3vvftu4NbbCWfW = Zend_Registry::get('w4m70Yt0hYUdlM2r2lrrIuY0zgKG4A3vvftu4NbbCWfW');
                $w4m70Yt0hYUdlM2r2lrrIuY0zgKG4A3vvftu4NbbCWfW = $w4m70Yt0hYUdlM2r2lrrIuY0zgKG4A3vvftu4NbbCWfW[$groupData['licenseType']];
			}
			catch( Exception $e )
			{

				$skipExistingAccountChecks = true;

			}

			
			//check for all expired, then check for enough for this license type.
			if( !$skipExistingAccountChecks && $groupData['licenseType'] == 0 )
			{ //check if the group that this user belongs to had valid license seat (not expired and seats >= 0)

				$this->userErrorMessage = $this->view->translate("License Invalid, or no license installed.");
				$logonAttemptAllowed = false;
				
			}
			else if( !$skipExistingAccountChecks && Zend_Registry::get('w4m70Yt0hYNduM2r2lrrIuY0zgKG4A3vvftl4UbbCWfW') )
			{ //check if the all licenses expired)

				$this->userErrorMessage = $this->view->translate("License Expired.");
				$logonAttemptAllowed = false;
				
			}
//			else if( !$skipExistingAccountChecks && ($w4m70Yt2hYUduM2r0lrrIuY0zgKG4A3vvftl4NbbCWfW==0 || $w4m70Yt0hYUdlM2r2lrrIuY0zgKG4A3vvftu4NbbCWfW<0 ) )
//			{ //check if the group that this user belongs to had valid license seat (not expired and seats >= 0)
//
//				$this->userErrorMessage = $this->view->translate("License Exceeded.");
//				$logonAttemptAllowed = false;
//				
//			}
			else if( !$skipExistingAccountChecks && $userData['UserStatus'] == '1' )
			{ //check if Webmail disabled on account

				$this->userErrorMessage = $this->view->translate("Account disabled.");
				$logonAttemptAllowed = false;
				
			}
			else if( !$skipExistingAccountChecks && $groupData['Webmail'] != '1' )
			{ //check if Webmail disabled on account

				$this->userErrorMessage = $this->view->translate("Webmail disabled for this account.");
				$logonAttemptAllowed = false;

			}
			else if( is_array(@unserialize($request->getParam('licenseType'))) )
			{ //check if the all licenses expired)

				$this->userErrorMessage = $this->view->translate("License Expired.");
				$logonAttemptAllowed = false;
				
			}

			// retrieve the configuration
			$requestedServer = strtolower( $f->filter($request->getParam('requestedServer')) );
			$userLanguage = $f->filter($request->getParam('Language'));
			$localDomains = domains::getList();

			$config = Zend_Registry::get('config');
			$this->globalConfig = $config->global;

			$remoteDomains = isset($this->globalConfig['remoteDomains'])
				?
					(strpos($this->globalConfig['remoteDomains'], "\n") === false ? $this->globalConfig['remoteDomains'] : explode("\n", $this->globalConfig['remoteDomains']) )
				:
					array();
			if( isset($this->globalConfig['remoteServers']) && is_array($this->globalConfig['remoteServers']) )
				$this->globalConfig['remoteServers'] = implode("\n", $this->globalConfig['remoteServers']);
			$remoteServers = isset($this->globalConfig['remoteServers'])&&strlen($this->globalConfig['remoteServers'])>0
				?
					(strpos($this->globalConfig['remoteServers'], "\n") === false ? $this->globalConfig['remoteServers'] : explode("\n", $this->globalConfig['remoteServers']) )
				:
					array();


			Zend_Registry::get('log')->debug( __METHOD__ . ' #' . __LINE__ . ': $logonAttemptAllowed= ' . ($logonAttemptAllowed?'Allowed':'Not Allowed') );
			Zend_Registry::get('log')->debug( __METHOD__ . ' #' . __LINE__ . ': $this-userErrorMessage= ' . $this->userErrorMessage);

			// Check the login logic against the Webadmin rules, local account, external and hijack attempts
			if( $logonAttemptAllowed )
				$logonAttemptAllowed = $this->logonAttemptAllowed($localDomains, $remoteDomains, $remoteServers, $emailDomain, $requestedServer, $userData['Account']);

            Zend_Registry::get('log')->debug( __METHOD__ . ' #' . __LINE__ . ': $logonAttemptAllowed= ' . ($logonAttemptAllowed?'Allowed':'Not Allowed') );
			Zend_Registry::get('log')->debug( __METHOD__ . ' #' . __LINE__ . ': $this-userErrorMessage= ' . $this->userErrorMessage);


			if( $logonAttemptAllowed )
			{
				//CONSIDER: moving all the auth and UserSession related stuff into models and library
				//try authenticate logon via allowed methods
				//1. Normal localhost Maildir user
				//2. Remote POP3 Server
				//3. Remote IMAP Server
				//4. LDAP Server????
				//5. SSO
				$this->log->debug( __METHOD__ . ' #' . __LINE__ . ' Logon attempt allowed' );

				//we will actually have to attempt to connect to the email servers as part of logon
				//if not local user try login first and only proceed if success
				$userData['namespaceName'] = 'MailSource1'; //always this until multi account handling enabled
				//(Ben opted to only impliment user login to single email box/Account)
				//below will need to change if app updated to handle multiple mail store accounts on one user account, or different user account and password to that for mail store
				$userData['password'] = $password;
				$userData['port'] = (isset($port) && !empty($port))?$port:null;
				$userData['host'] = $this->requestedServer; // Use the requestedServer as specified on the login check function
				$userData['Account'] = $emailName . ( (isset($emailDomain) && !empty($emailDomain))?'@' . $emailDomain:'' );
				$userData['emailName'] = $emailName;
				$userData['user'] = $userData['Account'];
				$userData['tmpFolderBaseName'] = $this->globalConfig['tmpFolderBaseName'];
				$userData['dirname'] = (isset($userData['MailDir'])?$userData['MailDir']:'');

				//determine which protocol to use
				$requestMailType = $request->getParam('MailType');

				//sanitize
				if( $requestMailType == 'IMAPS' )
				{

					// Use 'SSL' for old fashioned IMAPS on port 993
					// Use 'TLS' for IMAP with STARTTLS command
					$userData['ssl'] = 'SSL';
					$requestMailType = 'IMAP';
					$userData['protocol'] = 'IMAP';

				}
				else
				{

					$requestMailType = 'IMAP';
					$userData['protocol'] = 'IMAP';

				}

				$userData['CalDavUser'] = $userData['Account'];
				//$userData['CalDavPass'] = $password;
				$mergedUserData = array_merge($this->globalConfig,  $userData );
				$auth = Zend_Auth::getInstance();
				$adapter = new Zend_Auth_Adapter_DbTable($dbAdapter);
				$adapter->setTableName($dbTables->UserSession);
				$adapter->setIdentityColumn('Account');
				$adapter->setCredentialColumn('Password');
				$adapter->setIdentity($userData['Account']);
				
				$userPasswordEncryptionTypeCurrent = Zend_Registry::get('config')->global['userPasswordEncryptionType'];

				if($userPasswordEncryptionTypeCurrent == "ENCRYPTED")
				{
					$adapter->setCredential(Atmail_Password::hexaes_encrypt($password));
				}
				else if($userPasswordEncryptionTypeCurrent != "PLAIN")
				{
					$adapter->setCredential($password);				
					$adapter->setCredentialTreatment('MD5(?)');
				}
				else
				{
					// try plain text password match
					$adapter->setCredential($password);
				}

				$result = $auth->authenticate($adapter);
				
				$this->_helper->pluginCall('postAuthenticate', array(&$result));

				$select = $dbAdapter->select()
					->from($dbTables->Users)
					->join($dbTables->UserSettings, " {$dbTables->Users}.Account = {$dbTables->UserSettings}.Account")
					->join($dbTables->UserSession, "{$dbTables->Users}.Account = {$dbTables->UserSession}.Account")
					->where("{$dbTables->Users}.Account = " . $dbAdapter->quote( $userData['user'] ));
				$userDataRows = $select->query()->fetchAll();
				Zend_Registry::set('UserSettings', (isset($userDataRows[0])?$userDataRows[0]:array()) );

				unset( $userDataRows[0]['SessionData'] );
				$logonSuccess = false;

				if( $result->isValid() )
				{
					$auth->getStorage()->clear();
					// success: store database row to auth's storage system excl. password along with remining user data
					$authData = $adapter->getResultRowObject(null, array('Password', 'SessionData') );

				 	if( count($userDataRows) > 1 )
					{
						// for the time being we are going to use the last entry.
						//throw new Exception('Invalid user credential row count returned');
						$userDataRows = $userDataRows[0];
					}

					if( count($userDataRows) == 1 )
					{
						//TODO: create new $mergedUserData to keep size and sensitiveness of cachable data down
						//try connect and get message count
						$this->log->debug( 'About to try login' );

						$mergedUserData['folder'] = null; //mainly for testing reauthing on an account that may have session folder stored of just deleted folder
						//TODO: can remove above line to facilitate reauth back to previously selected folder after adding graceful fail back to default folder on selected folder delete

						if( !$this->_login($mergedUserData) )
						{
							$this->_helper->pluginCall('loginFail');

							$this->view->error = 'Failed logging in with ' . $userData['user'];
							$this->log->debug( $this->view->error );
							$this->_forward('index', null, null, array('retrying' => '1'));
							return;
						}

						$this->AtmailMailStorageMain->saveCache();

						$this->log->debug('Logon with ' . $userData['user'] . ' succeeded.');
						$logonSuccess = true;
					}
					else
					{
						//no dependant user settings found so fail
						$this->view->error = $this->view->translate('Local user not properly setup. Webadmin Administrator needs to recreate account before logon will succeed.');
						$this->log->debug($this->view->error);
						$this->_helper->pluginCall('loginFail');
						$this->_forward('index', null, null, array('retrying' => '1'));
						return;
					}
				}
				else
				{
					//try connect and get message count
					if( !$this->_login($userData) )
					{
						$this->_helper->pluginCall('loginFail');
						$jsoncallback = $request->getParam('jsoncallback');
						if( $jsoncallback || $this->getRequest()->isXmlHttpRequest() ) {
							$status = array('auth' => 'fail', 'error' => $this->view->error);
							$callback_wrapper_head = '';
							if( $jsoncallback != '')
							{
								$callback_wrapper_head = $jsoncallback . '(';
							}
							$callback_wrapper_tail = '';
							if( $jsoncallback != '')
							{
								$callback_wrapper_tail = ')';
							}

							$json =  $callback_wrapper_head . Zend_Json::encode($status) . $callback_wrapper_tail;
							$this->getResponse()->setHeader('Content-Type','application/x-javascript')->appendBody($json);
							$this->_helper->viewRenderer->setNoRender();
						} else {
							// Else, if a regular login pass the login creds back
							//$this->_forward('index', null, null, array('retrying' => '1'));
							$this->_forward('index', null, null, array(
								'retrying' => '1',
								'errorMsg'=>'Login failed. Username or password is not correct.',
							));

						}
						return;
					}

					$this->AtmailMailStorageMain->saveCache();
					
					$this->log->debug('Logon with username ' . $userData['user'] . ' succeeded.');

					$userData['MailType'] = $userData['protocol'];
					$mergedUserData = array_merge($config->defaultUserSettings, $userData);
					$mergedUserData['name'] = $mergedUserData['Account'];


					//check unique Account doesnt already exist
					$userDataRows = $dbAdapter->select()
											  ->from($dbTables->Users)
											  ->where("Account = " . $dbAdapter->quote($mergedUserData['Account']) )
											  ->query()
											  ->fetchAll();

					if( count($userDataRows) > 0 )
					{

						$this->log->debug('Skip create Users record: Unique Account record already existed in Users table.');

					}
					else
					{

						$admin = new api( array('directApi' => 1) );
						$mergedUserData['isExternalUser'] = '1';
						$admin->userCreate( $mergedUserData );

					}


					$logonSuccess = true;
				}
			}
			//EO if( $logonAttemptAllowed )
			else
			{
				$this->_helper->pluginCall('loginFail');
			}


		} //EO if ($request->has('emailName') && $request->has('password'))

		if( $logonSuccess )
		{
			// clear all admin cookies (security)
			$global = Zend_Registry::get('config')->global;
			if(!array_key_exists('exclusive_sessions', $global) || (array_key_exists('exclusive_sessions', $global) && $global['exclusive_sessions']))
			{
				// unset cookies
				// we must blindly set the admin cookie here as we cannot access cookies from other paths, but we can write to them.
				setcookie(ATMAIL_ADMIN_NAMESPACE, '', 0, '/' . trim($this->view->siteBaseUrl, '/') . '/index.php/admin');
			}
			//if current session id already in use then regenerate up to 3 times to get unique SID
			if( !Zend_Registry::get('ATMAIL_UNIT_TESTING') )
			{
				
				$sessionSaveHandler = Zend_Session::getSaveHandler();
				$sessionSaveHandler->enforceUniqueSID($userData['user']);
				
			}
			
			//only update password if external user or ldap
			$dovecotSettings = Zend_Registry::get('config')->dovecot;
			$domain = substr($userData['Account'],strpos($userData['Account'], '@')+1);
			if( !in_array($domain, $localDomains) || $dovecotSettings['authType'] == 'ldap')
			{
				$externalUserPasswordEncryptionTypeCurrent = Zend_Registry::get('config')->global['externalUserPasswordEncryptionType'];

				if($externalUserPasswordEncryptionTypeCurrent == "ENCRYPTED")
				{
					// store the password for usage by atmail subsystems
					$dbAdapter->update("UserSession", array("Password" => Atmail_Password::hexaes_encrypt($password)), "Account = " . $dbAdapter->quote($userData['Account']));
					$mergedUserData['password'] = Atmail_Password::hexaes_encrypt($password);
				}
				else
				{
					$dbAdapter->update("UserSession", array("Password" => $password), "Account = " . $dbAdapter->quote($userData['Account']));
				}
			}

			$updateData = array();

			// If the account has specified a new language on the login-page, update the DB
			if( !empty($userLanguage) )
				$updateData['Language'] = $userLanguage;

			// If the account has specified a new Mailserver
			if( !empty($this->requestedServer) )
				$updateData['MailServer'] = $this->requestedServer;

			// If the account has specified a new MailType
			if( !empty($requestMailType) )
				$updateData['MailType'] = $requestMailType;

			$updateData['UseSSL'] = (isset($userData['ssl'])&&$userData['ssl']?'1':'0');

			// Update the UserSettings, the Mailserver, MailType or Language ( or all )
			if( count($updateData) > 0 ) {
				$dbAdapter->update($dbTables->UserSettings, $updateData, "Account = " . $dbAdapter->quote($userData['user']));
			}

			// Update LastLogin in DB
			$dbAdapter->update($dbTables->UserSession, array('LastLogin' => date('Y-m-d H:i:s')), "Account = " . $dbAdapter->quote($userData['user']));

			//UPDATE dependant account credentials
			//Zend_Registry::get('log')->debug( "\n" . print_r($mergedUserData, true) . "\n" . __METHOD__ . ' #' . __LINE__ . ": \$mergedUserData \n");
			//Zend_Registry::get('log')->debug( __METHOD__ . ' #' . __LINE__ . ": Setting users SIP password to: " . $password);
			if( array_key_exists('SIPPswdOverwrite', Zend_Registry::get('config')->global ) && Zend_Registry::get('config')->global['SIPPswdOverwrite'] == '1')
			{
				$dbAdapter->update("ServiceAccounts", array('Password' => $password), "Account = " . $dbAdapter->quote($mergedUserData['Account']) );
			}
			
			$auth->getStorage()->write($mergedUserData);
			Zend_Registry::set('Account', $mergedUserData['Account']);

			// Log the action as a Webmail request
			require_once 'application/models/log.php';
			$logEntry = new logEntry($userData['user']);
			$logEntry->insert("Log_Login", array('LogType' => '5', 'LogIP' => $_SERVER["REMOTE_ADDR"], 'Account' => $userData['user'] ) );

			$this->_helper->pluginCall('loginSuccess');

			// If we are a json call from the API, return a JSON packet for the authentication
			if( ( $request->getParam('jsoncallback') || $request->getParam('expectJson') ) && !$request->has('force-webmail')) {

				// Query our SessionID
				$sessionId = session_id();

				// Append our SessionID (for JSON requests)
				$status = array('auth' => 'success', 'Account' => $userData['user'], 'SessionID' => $sessionId);

				if($request->getParam('expectSettings'))
				{
					$settings = Zend_Registry::get('UserSettings');
			        if(!empty($settings['Signature'])) {
			    		require_once('class.html2text.inc');
			        	$html2text = new html2text( $settings['Signature'] );
			        	$settings['Signature'] = $html2text->get_text();
			        }
			        
					$hiddenKey = array('Password', 'SessionData', 'PasswordQuestion', 'SessionID', 'ChangePass');
					foreach($hiddenKey as $key) {
						unset($settings[$key]);
					}
					
					$profile = $this->getAbookProfile($userData['user']);
					
					$settings['profile'] = $profile;

					$status['settings'] = $settings;
				}

				$this->renderJson($status);


			} else {
				// iToy user who came via an ISP ?iosprofile link get the mobileconfig XML without entering webmail
				if ($request->has('iosprofile')) {

					return $this->_redirect('mail/settings/iosstart');

				} else {

					// Else, if a regular login pass the login creds back

					// Check if session is https and should be switched to http once auth is complete
					if (isset($_SERVER['HTTPS']) && $request->has('switchtohttp') && $request->getParam('switchtohttp') == 1)
					{					
						return $this->_redirect("http://{$_SERVER['SERVER_NAME']}{$_SERVER['SCRIPT_NAME']}/mail");
					}
				
					return $this->_redirect('mail');
					
				}
			}

		}
		else
		{
			$this->log->debug('Logon failed!');
			$this->view->error = $this->userErrorMessage;
			
			$this->_helper->pluginCall('loginFail');
			
			if( $request->getParam('jsoncallback') || $request->getParam('expectJson')) {

				$status = array('auth' => 'fail', 'error' => $this->view->error);

				$this->renderJson($status);

			} else {

				// Else, if a regular login pass the login creds back
				$this->_forward('index', null, null, array('retrying' => '1'));

			}

			return;
		}
	}
	
	public function getAbookProfile($account)
	{
		$contacts = new contacts( array('Account' => $account, 'largeInstall' => '0') );

		$query = array('UserEmail'=>$account, 'GroupID'=>0);
		$search_results = $contacts->searchContact($query);

		return $search_results? $search_results : null;
	}
		
	public function logoutAction()
	{
		// Check that user has registered
		if( $this->_getParam('badRegoData') )
		{
			$this->view->registered = false;
			$this->view->error = 'The ClientID or Email your gave upon registration is incorrect. Please re-enter your details or contact support@staff.atmail.com for assistance';
		}
		else
		{
			$this->view->registered = true;
			$this->view->message = $this->view->translate('Logout successful.');
		}

		$auth = Zend_Auth::getInstance();
		try
		{
			// we may cause a session error here as
			// zend auth will try and auto start the session 
			// again, but it may already be destroyed.
			$userData = $auth->getIdentity();

			if($userData != NULL)
			{
				// Empty the Trash folder if required
				$settings = users::getUserSettings($userData['Account']);
				if ($defaultUserSettings['DeleteTrashOnLogout'] == 1 && !$this->_getParam('trashEmptied')) {
					$this->_forward('emptyfolder', 'mail', 'mail', array('deleteTrashOnLogout' => 1));
					return;			
				}
				
				//garbage collection
				//remove users tmp folder
				//but only if not using memcache
				$global = Zend_Registry::get('config')->global;
				$useMemcache = (array_key_exists('cacheType', $global) && $global['cacheType'] == 'memcache');
				if(!$useMemcache)
				{
					$usersTmpFolder = users::getTmpFolder();
					self::removeFolderRecursive($usersTmpFolder);
				}
			}

            @$auth->clearIdentity();
			@Zend_Session::destroy(true);

        }
		catch(Exception $e)
		{
			// ignore the error
			$userData = NULL;	
		}

        setcookie('chtrsessionid', false, 315554400, '/');
        setcookie('chtrpkey', false, 315554400, '/');

        header( 'Location: http://atmail.local', true, 302 );
        exit();

        // $this->_forward('index', 'index', 'mail');
	}

	private static function removeFolderRecursive($dir)
	{
		if($dir == '' || $dir == 'tmp') return;
		
		$dh = @opendir($dir);
		while( false !== ($dirItem = @readdir($dh)) )
		{

			if ($dirItem == '.' || $dirItem == '..')
			{

				continue;

			}

			if( is_link($dir . DIRECTORY_SEPARATOR . $dirItem) )
			{

				unlink($dir . DIRECTORY_SEPARATOR . $dirItem);

			}
			elseif( is_dir($dir . DIRECTORY_SEPARATOR . $dirItem) )
			{

				// recurse subdirectory; call of function recursive
				self::removeFolderRecursive( $dir . DIRECTORY_SEPARATOR . $dirItem );

			}
			elseif( is_file($dir . DIRECTORY_SEPARATOR . $dirItem) )
			{

				@unlink($dir . DIRECTORY_SEPARATOR . $dirItem);

			}

		}
		@closedir($dh);
		if( @rmdir($dir) )
		{

			return true;

		}
		return false;

	}

	private static function removeExpiredFilesRecursive( $dir )
	{

		// if $dir not a string or trying to reach parent dirs
		if( !is_string($dir) || strpos($dir, '../') !== false )
		{

			return;

		}
		if( substr($dir,-1) == DIRECTORY_SEPARATOR )
		{

			$dir = substr($dir,0,-1);

		}

		$timeout = "7200";	// Number of seconds (use 7200 for 2hrs)
		$pgpTimeout = "1800"; // Number of secs for PGP files
		$cacheTimeout = "1800"; // Number of secs for PGP files

		$time = time();
		$dirList = @scandir($dir);
		if( is_array($dirList) )
		{

			foreach ($dirList as $dirItem)
			{


				// Don't delete the . , .. , .htaccess in the root dir ( in case users have in the httpd.conf Options Indexes on, we don't want people surfing the tmp dir via the Web!)
				if(
					strpos($dirItem, '../') !== false ||
					$dirItem == "." ||
					$dirItem == ".." ||
					$dirItem == '.htaccess' ||
					$dirItem == ".svn" ||
					strpos($dirItem, 'index.html') !== false
				)
				{

					continue;

				}

				if( is_dir($dir . DIRECTORY_SEPARATOR . $dirItem) )
				{

					self::removeExpiredFilesRecursive($dirItem);
					continue;

				}

				$atime = fileatime($dir . DIRECTORY_SEPARATOR . $dirItem);
				$mtime = filemtime($dir . DIRECTORY_SEPARATOR . $dirItem);
				$secs = $time - $mtime;

				if ( $secs > $timeout )
				{

					// If older than 2 hrs , delete!
					unlink($dir . DIRECTORY_SEPARATOR . $dirItem);

				}
	            elseif( strpos($dirItem, '.ht') === false && ($secs > $pgpTimeout) )
				{

					// If pgp files are older than 30 minutes, delete
					unlink($dir . DIRECTORY_SEPARATOR . $dirItem);

				}
				elseif( strpos($dirItem, 'cache') !== false && ($secs > $cacheTimeout) )
				{

					// If the cache files are older then 1hr, delete
					unlink($dir . DIRECTORY_SEPARATOR . $dirItem);

				}

			}

		}

	}

	public function registerAction()
	{
		// Get POST data and verify it's valid
		$f = new Zend_Filter_StripTags();
		$request	= $this->getRequest();
		$downloadId = $f->filter($request->getPost('downloadid'));
		$email	  = $f->filter($request->getPost('email'));
		$username   = $f->filter($request->getPost('username'));
		$password   = $request->getPost('password');
		$password2  = $request->getPost('password2');

		$svnPass = '';

		if( strlen($password) < 6 )
		{
			$this->view->error = 'Password must be at least 6 characters in length.';
			$this->render('index');
			return;
		}

		if( $password != $password2 )
		{
			$this->view->error = 'Passwords did not match';
			$this->render('index');
			return;
		}

		if( strlen($username) < 1 )
		{
			$this->view->error = 'Please specify a username';
			$this->render('index');
			return;
		}

		if( strlen($downloadId) < 4 )
		{
			$this->view->error = 'Please specify a ClientID';
			$this->render('index');
			return;
		}

		if( strlen($email) < 6 || !strpos($email, '@') )
		{
			$this->view->error = 'Please specify the email address you used when purchasing';
			$this->render('index');
			return;
		}

		// Access the XML_RPC registration service to create and get back
		// our svn login details
		require_once('XML/RPC.php');

		$params = array(
			new XML_RPC_Value($downloadId, 'string'),
			new XML_RPC_Value($email, 'string')
		);

		$msg = new XML_RPC_Message('register', $params);

		$client = new XML_RPC_Client('/RPC/archive-rego-service.php', 'https://secure.calacode.com');
		$resp = $client->send($msg);

		// Ignore errors stemming from no internet access (likely cause of no response object)
		if( !($resp instanceof XML_RPC_Response) )
		{
			// do nothing
		}
		else
		{
			// If we got a response check for any errors
			if( !$resp->faultCode() )
			{
				// All is good!
				$val = $resp->value();
				$svnPass = $val->scalarval();
			}
			else
			{
				// we have an error
				$this->view->error = $resp->faultString();
				$this->render('index');
				return;
			}
		}
		try
		{
			require_once 'application/models/config.php';
			config::save('reg', array('svnPass' => $svnPass, 'downloadId' => $downloadId, 'email' => $email));
		}
		catch ( Exception $e )
		{
			$this->view->errors = array( 'Failed saving config: ' . $e->getMessage() );
			$this->render('global/jsonresponse', null, true);
			return;
		}

		$adminData = array(
			'username'  => $username,
			'password'  => md5($password),
			'mainGroup' => 'admin',
			'level'	 => 1,
			'enabled'   => 1
		);

		try
		{
			$dbTables = new dbTables();
			$dbAdapter = Zend_Registry::get('dbAdapter');

			// Write the MD5/pass to the Exim configure file for SMTP authentication
			$write = @fopen("/usr/local/atmail-archive/mailserver/.pass", 'w');

			fwrite($write, "admin: " . md5($password));
			fclose($write);
		}
		catch (Exception $e)
		{
			// ignore error, no database entries changed
		}

		$this->_forward('processlogin', 'auth', 'mail', array('fwdFromRego'=>1));
	}


	private function generate_demouser($domain)
	{
		$dbAdapter = Zend_Registry::get('dbAdapter');

		$username = 'demo_';

		$alpha = array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'z', 'u', 'z', 1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
			$numbers = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');

			for ($i=0; $i<3; $i++)
			{
			$num = rand(0,24);
			$username .= $alpha[$num];
			}

			$num = rand(0,9);
			$username .= $numbers[$num];

			// If the account exists already, generate another one until unique
			if($dbAdapter->fetchOne("select Account from UserSession where Account=" . $dbAdapter->quote($username . '@' . $domain)))
			return $this->generate_demouser($domain);
			else
			return $username;
	}

	private function _isRegistered()
	{
		$regData = Zend_Registry::get('config')->reg;
		return (isset($regData['downloadId']) && !empty($regData['downloadId']));
	}

	private function _login(&$mergedUserData)
	{
		// call preLogin event, plugins can override this login code here.
		// If the plugin wants to completely override this function then
		// it should return the string "ok" for a successful login or "fail"
		// for an unsuccessful login. If anything else is returned this function
		// will still be executed.
		$result = $this->_helper->pluginCall('preLogin', array($mergedUserData));
		if (!empty($result)) {
		   if ($result == "ok") {
			   return true;
		   } elseif ($result == "fail") {
			   return false;
		   } elseif (is_array($result)) {
                       $mergedUserData = $result[0][0];
                   }
		}

		// try user@domain first then just user if it fails
		$tries = 0;
		do
		{

			try
			{

				$this->AtmailMailStorage = Atmail_Mail_Storage::getInstance($mergedUserData);
				$this->AtmailMailStorageMain = $this->AtmailMailStorage->getMailStore($mergedUserData['namespaceName']);
				$this->AtmailMailStorageMain->connect();
				$this->view->notices[] = 'Login successful. ' . $this->AtmailMailStorageMain->countMessages() . ' messages';
				return true;

			}
			catch (Exception $e)
			{
				//pass exception through if not able to connect to host.
				if( strpos($e->getMessage(), 'cannot connect to host') !== false )
				{
					$this->view->error = $this->view->translate('Invalid username or password. Please check your details and try again.');
					$this->log->debug($this->view->error);
					return false;
				}

				if(get_class($this->AtmailMailStorage) == 'Atmail_Mail_Storage')
				{
					$this->AtmailMailStorage->destroy();
				}

				if( $tries == 0 )
				{
					$mergedUserData['user'] = ($mergedUserData['user'] != $mergedUserData['emailName']) ? $mergedUserData['emailName'] : $mergedUserData['Account'];
					$tries++;
					continue;
				}

				$tries++;
				$this->view->error = $e->getMessage();
				$this->log->debug($this->view->error);
				return false;

			}

		}
		while ($tries < 2);
		return false;

	}

}
