<?php

/*
   +----------------------------------------------------------------------+
   | Authentication Class						  |
   | Author: Adam Goldberg                                                |
   +----------------------------------------------------------------------+
   | Copyright (c) 2008-2009 Adam J Goldberg                              |
   | This file is part of Lifebox.                                        |
   |                                                                      |
   | Lifebox 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 3 of the License, or    |
   | (at your option) any later version.                                  |
   |                                                                      |
   | Lifebox 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 Lifebox.  If not, see <http://www.gnu.org/licenses/>.     |
   +----------------------------------------------------------------------+
   | Authenticates users based on email addresses.			  |
   +----------------------------------------------------------------------+
	function Authenticate ($string=false){
        function setInfo($data,$quick=false){
        function commit(){
	function delete() {
	function isValid($url=false,$ip=false){
	function notAbused($ip=false,$url=false) {
	function getAll() {
        function getNameByIP() {
        function isAuthenticatedIP($ip) {
	function getUsers() {
	function _updateAccess($ip=false) {
	function _populate(){
	function setUniqueID() {
	function unsetUniqueID() {
	function getLevelsList($level=false) {
	function sendVerification($url=false) {
        function _generateKey(){
        function _isValidEmail($email){
   +----------------------------------------------------------------------+
*/

require_once 'Mail.inc.php';
class Authenticate {

	var $email;
	var $uniqueid;
	var $varid;
	var $info;
	var $new_user;

	/*****************************************************************
         * Constructor
         *****************************************************************/
	function Authenticate ($string=false){
	global $ERROR;
	global $CONFIG;

		#CHECK FOR COOKIE KEY
		if (strlen($string) == 32){
			$this->uniqueid = $string;
			$this->new_user = false;
		
		#CHECK FOR VARID KEY
		} elseif (strlen($string) == 16 AND !strpos($string,'@')) {
			$this->verid = $string;
			$this->new_user = false;

		#CHECK FOR PASSWORD
		} elseif ($string === $CONFIG['admin_password']) {
	
			#BYPASS
			$this->email = $CONFIG['admin_email'];
			$this->new_user = false;

		#CHECK FOR SUPPLIED EMAIL ADDRESS
		} elseif ($string) {
			if ($string === $CONFIG['admin_email'] ){
				$ERROR['reserved'] = 'This e-mail cannot be used for authentication';
				return false;
			} else {
				$this->email = $string;
			}
		} else {
			$this->new_user = true;
		}

		$this->_populate();
	}

	/*****************************************************************
         * Adds a new member.
         *****************************************************************/
        function setInfo($data,$quick=false){
        global $ERROR;
        global $CONFIG;

                foreach ($data as $key => $value){

			$value = strip_tags($value);

                        if ($this->info[$key] == 'on'){
                                $this->info[$key] = 1;
                        }

			if ($key === 'level') {
                                $levels_list = $this->getLevelsList($value);
                                $value = $levels_list['level'];
                        }

			if ($key === 'enabled' AND !is_numeric($value)) {
				if (strtolower($value) === 'enabled') {
					$value = '1';
				} elseif (strtolower($value) === 'disabled') {
					$value = '0';
				}

			}

			$this->info[$key] = strip_tags(trim($value));
                }

                if ($this->new_user){

                        #NAME -- check not empty
                        if (empty($this->info['first']) AND !$quick){
                                $ERROR['first'] = 'Please enter first name.';
                                return false;
                        }

			#NAME -- check not empty
                        if (empty($this->info['last']) AND !$quick){
                                $ERROR['last'] = 'Please enter last name.';
                                return false;
                        }

                        #E-MAIL -- check not empty
                        if (empty($this->info['email'])){
                                $ERROR['email'] = 'Please enter e-mail.';
                                return false;
                        }

                        #E-MAIL -- check proper format
                        elseif (!$this->_isValidEmail($this->info['email'])){
                                $ERROR['email'] = 'Invalid e-mail.';
                                return false;
                        }

                        #Generate the member_id
                        $this->info['id'] = $this->_generateKey();
                        $this->uniqueid = $this->info['id'];

                } else {

                        //If Updating
                }

        }

	/*****************************************************************
         * Adds a new member.
         *****************************************************************/
        function commit(){
        global $CONFIG;
        global $ERROR;
        global $db;

                if (!empty($ERROR)){
                        return false;
                }

                if ($this->new_user){

                        #Generate the current time
                        $date = time();

                        #Build the query.
                        foreach ($this->info as $key=>$value){

                                #Exceptions
                                if ($key !== 'agree'){
                                        $insert_into .= "$key,";
                                        $values .= "'$value',";
                                }
                        }

                        #strip the last comma
                        $insert_into = substr($insert_into,0,-1);
                        $values = substr($values,0,-1);


                        #First, insert the query into the Central Database
                        $query = "INSERT INTO ".$CONFIG['tbl_access']."
                                  ($insert_into)
                                  VALUES ($values)";
                        $result = $db->Execute($query);

                        if ($db->ErrorNo() == 1062){
                                $ERROR['email'] = 'E-Mail already registered';
                                return false;
                        }

                        if ($result){

                                return true;
                        } else {
                                return false;
                        }

                } else {

                        #Build the query.
                        foreach ($this->info as $key=>$value){
                                $update .= $key." = '$value',";
                        }

                        #Strip the last comma
                        $update = substr($update,0,-1);

                        #Update Local
                        $query = "UPDATE ".$CONFIG['tbl_access']."
                                  SET $update
                                  WHERE id = '".$this->uniqueid."'";
                        $result = $db->Execute($query);

                        if ($result){
                                return true;
                        } else {
                                return false;
                        }
                }
        }

	/*****************************************************************
	 * Delete a user from the database
         *****************************************************************/
	function delete() {
	global $CONFIG;
	global $db;

		$query = "DELETE FROM ".$CONFIG['tbl_access']."
			  WHERE id = '".$this->uniqueid."'";
		$result = $db->Execute($query);

		if ($result) {
			return true;
		} else {
			return false;
		}
	}
	
	/*****************************************************************
         * Checks if this is a valid user by authenticating the email
	 *****************************************************************/
	function isValid($url=false,$ip=false){
	global $CONFIG;
	global $db;
	global $ERROR;

		if ($ERROR){
			return false;
		}

		if (!$url) {
			$url='http://'.$_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI'];
		}

		if (!$ip) {
			$ip = $_SERVER['REMOTE_ADDR'];
		}

		#Is this a valid e-mail and is the user enabled?
		$query = "SELECT COUNT(*) as num
			  FROM ".$CONFIG['tbl_access']."
			  WHERE email = '".$this->email."' AND
				enabled = 1";
		$row = $db->getRow($query);

		if ($row['num'] == 0) {
			if ($_POST) {
				$ERROR['not_on_list'] = 'The e-mail you entered is not on the access list.  Did you enter your e-mail correctly? '.
							'Do you have a different e-mail (school, work, personal)?  If not, send me a message!';
			} else {
				$ERROR['sess_expired'] = 'Your session has expired, probably because you logged in on another computer or browser.';
			}
			return false;
		}

		#Prevent logging for any file accessed that's not php
		#it's just not necessary.  This will get rid of most
		#jpg, avi, cr2, etc ...
		if (strtolower(substr($url,-3)) === 'jpg' OR
		    strtolower(substr($url,-3)) === 'avi' OR 
		    strtolower(substr($url,-3)) === 'cr2') {
			$log = false;
		} else {
			$log = true;
		}

		#Log This Attempt, whether it's valid or not!  But don't log accesses to lock
		if ($log AND strlen($this->email) > 0 AND !strpos($_SERVER['HTTP_HOST'],$CONFIG['domain'].'/?id')){
			$query = "INSERT INTO ".$CONFIG['tbl_logins']."
				  (email,datetime_auth,ip,host,success,url)
				  VALUES ('".$this->email."',
					  now(),
					  '".$ip."',
					  '".gethostbyaddr($ip)."',
					  '".$row['num']."',
					  '".$url."')";
			$result = $db->Execute($query);
		}
	
		#Return the result	
		if ($row['num'] == '1'){
			$this->_updateAccess($ip);
			return true;
		} else {
			setcookie('uniqueid','',-60000,'/',$CONFIG['cookie_domain']);
			$ERROR['invalid'] = 'The email you have entered is not on the access list.';
			return false;
		}

	}

	/*****************************************************************
         * Does a few different abuse checks for first-time logins
         *****************************************************************/
	function notAbused($ip=false,$url=false) {
	global $CONFIG;
	global $db;
	global $ERROR;

		if ($this->email === $CONFIG['admin_email']) {
			return true;
		}

		if (!$ip) {
			$ip = $_SERVER['REMOTE_ADDR'];
		}

		## CHECK 1
		#Check for less than 20 IPs per Email in the last 30 days
		#This should prevent nearly all chain letter attempts
		$query = "SELECT COUNT(DISTINCT ip) as num
			  FROM ".$CONFIG['tbl_logins']."
			  WHERE email = '".$this->email."' AND
				datetime_auth > date_sub(now() , INTERVAL ".$CONFIG['auth_ips_per_email_long_period']." DAY)";
		$row = $db->getRow($query);
		if ($row['num'] >= $CONFIG['auth_ips_per_email_long']){
			$ERROR['too_many_different_people'] = $CONFIG['auth_ips_per_email_long_msg'];
			$this->sendVerification($url);
			return false;
		}

		## CHECK 2
		#If more than 3 IPs in the last 24 hours,
		#Send a verification e-mail
                $query = "SELECT COUNT(DISTINCT ip) as num
                          FROM ".$CONFIG['tbl_logins']."
                          WHERE email = '".$this->email."' AND
                                datetime_auth > date_sub(now() , INTERVAL ".$CONFIG['auth_ips_per_email_short_period']." HOUR)";
                $row = $db->getRow($query);
                if ($row['num'] > $CONFIG['auth_ips_per_email_short']){
                        $ERROR['verification_necessary'] = $CONFIG['auth_ips_per_email_short_msg'];
                        $this->sendVerification($url);
                        return false;
                }

		## CHECK 3
		# Check for less than 5 emails from the same IP in
		# 24 hours.  This should eliminate brute force attempts
		$query = "SELECT COUNT(DISTINCT email) as num
                          FROM ".$CONFIG['tbl_logins']."
                          WHERE ip = '".$ip."' AND
                                datetime_auth > date_sub(now() , INTERVAL ".$CONFIG['auth_emails_per_ip_short_period']." DAY)";
                $row = $db->getRow($query);
                if ($row['num'] > $CONFIG['auth_emails_per_ip_short'] ){
			$ERROR['too_many_logins'] = $CONFIG['auth_emails_per_ip_short_msg'];
                        return false;
                }

		## CHECK 4
		# Check that someone has not used this same e-mail address 
		# For access within the last 10 minutes
                $query = "SELECT COUNT(DISTINCT ip) as num
                          FROM ".$CONFIG['tbl_logins']."
                          WHERE email = '".$this->email."' AND
				ip != '".$ip."' AND
				ip NOT LIKE '".$CONFIG['bypass_ip1']."%' AND
				ip NOT LIKE '".$CONFIG['bypass_ip2']."%' AND
                                datetime_auth > date_sub(now() , INTERVAL ".$CONFIG['auth_last_access_period']." MINUTE)";
                $row = $db->getRow($query);
                if ($row['num'] > 0){
                        $ERROR['concurrent_user'] = $CONFIG['auth_last_access_period_msg'];
                        return false;
                }    

		## CHECK 5
                # Check for less than 5 emails from the same IP in
                # 30 Days.  This should eliminate brute force attempts
                $query = "SELECT COUNT(DISTINCT email) as num
                          FROM ".$CONFIG['tbl_logins']."
                          WHERE ip = '".$ip."' AND
				success = 0 AND
                                datetime_auth > date_sub(now() , INTERVAL ".$CONFIG['auth_emails_per_ip_long_period']." DAY)";
                $row = $db->getRow($query);
                if ($row['num'] > $CONFIG['auth_emails_per_ip_long']){
                        $ERROR['too_many_logins'] = $CONFIG['auth_emails_per_ip_long_msg'];
			$this->sendVerification($url);
                        return false;
                }

		return true;
	}

	/*****************************************************************
         * Access method for retreiving user data
         *****************************************************************/
	function getAll() {
	
		return $this->info;

	}

	/*****************************************************************
	 * Return Predicted Name
         *****************************************************************/
        function getNameByIP() {
	global $CONFIG;
	global $db;

		$query = "SELECT first,last
			  FROM ".$CONFIG['tbl_access']."
			  WHERE ip = '".$_SERVER['REMOTE_ADDR']."'
			  ORDER BY datetime_access DESC";
		$row = $db->getRow($query);

		if ($row){
			return $row;
		} else {
			return false;
		}
	}

	/*****************************************************************
	 * Determine if a current IP is logged in or not
         *****************************************************************/
        function isAuthenticatedIP($ip) {
        global $CONFIG;
        global $db;

		if (preg_match("/".$CONFIG['bypass_ip1']."/",$ip) OR
		    preg_match("/".$CONFIG['bypass_ip2']."/",$ip)) {
			return true;
		}

		#Find the unique id of the last person logged
		#in from this address.
                $query = "SELECT uniqueid
                          FROM ".$CONFIG['tbl_access']."
                          WHERE ip = '$ip'
			  ORDER BY datetime_login DESC LIMIT 1";
                $row = $db->getRow($query);

                if (strlen($row['uniqueid']) === 32){
                        return true;
                } else {
                        return false;
                }
        }

	/*****************************************************************
	 * Return an array of all users from the database
         *****************************************************************/
	function getUsers() {
	global $CONFIG;
	global $db;

		$query = "SELECT a.*,
				 sa.title as level_title
			  FROM ".$CONFIG['tbl_access']." a,
			       ".$CONFIG['tbl_s_access']." sa
			  WHERE a.level = sa.level
			  ORDER BY first
			 ";
		$results = $db->getAll($query);

		if ($results) {
			return $results;
		} else {
			return false;
		}
	}
	
	/*****************************************************************
         * Updates the database with a new key and last access times for
	 * a user
         *****************************************************************/
	function _updateAccess($ip=false) {
	global $CONFIG;
	global $db;

		if (!$ip) {
			$ip = $_SERVER['REMOTE_ADDR'];
		}

		$query = "UPDATE ".$CONFIG['tbl_access']."
			  SET ip = '".$ip."',
			      host = '".gethostbyaddr($ip)."',
			      datetime_access = now(),
			      num_hits = num_hits + 1
			  WHERE email = '".$this->email."'";
		$result = $db->Execute($query);
		
		if ($result){
			return true;
		} else {
			return false;
		}
	}

	/*****************************************************************
         * Populates the class with missing information
         *****************************************************************/
	function _populate(){
	global $CONFIG;
	global $db;
	global $_INFO;

		if (strlen($this->uniqueid) == 32){
			$query = "SELECT a.*,
					 sa.title as access_title
				  FROM ".$CONFIG['tbl_access']." a,
				       ".$CONFIG['tbl_s_access']." sa
				  WHERE uniqueid = '".$this->uniqueid."'
					AND a.level = sa.level";
		} elseif (isset($this->verid) AND strlen($this->verid) == 16) {
			$query = "SELECT a.*,
                                         sa.title as access_title
                                  FROM ".$CONFIG['tbl_access']." a,
                                       ".$CONFIG['tbl_s_access']." sa
                                  WHERE verid = '".$this->verid."'
                                        AND a.level = sa.level";
		} else {
			$query = "SELECT a.*,
					 sa.title as access_title
				  FROM ".$CONFIG['tbl_access']." a,
				       ".$CONFIG['tbl_s_access']." sa
				  WHERE email = '".$this->email."'
					AND a.level = sa.level";
		}

		$row = $db->getRow($query);
		if ($row){
			$this->info = $row;
			$_INFO = $row;
			$this->email = $row['email'];

			if (isset($this->key)) {
				$this->key = $row['key'];
			}
		} else {
			return false;
		}
	}

	/*****************************************************************
         * Sets the cookie
         *****************************************************************/
	function setUniqueID() {
	global $CONFIG;
        global $db;

		$uniqueid = md5($this->email.mktime());
                $this->uniqueid = $uniqueid;

                $query = "UPDATE ".$CONFIG['tbl_access']."
                          SET uniqueid = '".$this->uniqueid."',
			      datetime_login = now(),
			      datetime_access = now(),
			      num_logins = num_logins + 1,
			      verid = '',
			      datetime_verid_sent = '0000-00-00 00:00:00'
                          WHERE email = '".$this->email."'";
                $result = $db->Execute($query);

		setcookie('uniqueid',$this->uniqueid,time()+60*60*24*30*12,'/',$CONFIG['cookie_domain']);

                if ($result){
                        return true;
                } else {
                        return false;
                }

	}

	/*****************************************************************
         * Removes all cookies
         *****************************************************************/
	function unsetUniqueID() {
	global $CONFIG;
	global $db;

		setcookie('uniqueid','',-60000,'/',$CONFIG['cookie_domain']);

		$query = "UPDATE ".$CONFIG['tbl_access']."
			  SET uniqueid = ''
			  WHERE email = '".$this->email."'";
		$result = $db->Execute($query);

	}

	/*****************************************************************
	 * Get Levels List
         *****************************************************************/
	function getLevelsList($level=false) {
	global $CONFIG;
	global $db;

		if (is_numeric($level)) {
			$where = "WHERE level = '$level'";
		} elseif ($level) {
			$where = "WHERE title = '$level'";
		} else {
			$where = '';
		}

		$query = "SELECT *
			  FROM ".$CONFIG['tbl_s_access']."
			  $where";

		if ($level) {
			$row = $db->CacheGetRow($CONFIG['cache_s_access'],$query);

			if ($row) {
				return $row;
			} else {
				return false;
			}
		} else {
			$results = $db->CacheGetAll($CONFIG['cache_s_access'],$query);
		
			if ($results){
				return $results;
			} else {
				return false;
			}
		}
	}

	/*****************************************************************
	 * Send verification e-mail to make sure this is the right person
         *****************************************************************/
	function sendVerification($url=false) {
	global $CONFIG;
	global $db;

		$query = "SELECT *
			  FROM ".$CONFIG['tbl_access']."
			  WHERE email = '".$this->email."'";
		$row = $db->getRow($query);

		#First, check that a verid hasn't already been sent
		$time = time();
		$diff = $time - strtotime($row['datetime_verid_sent']);
		if ($diff < (60 * 60 * 12)) {
			$ERROR['verid_sent'] = 'A verification has already been sent.  Please '.
					       'click the link in your e-mail';
			return false;
		}

		#If not, generate a verid, put it in the database, and e-mail it to the user
		$verid = substr(md5($this->email.time()),0,16);

		#Insert it into the database
		$query = "UPDATE ".$CONFIG['tbl_access']."
			  SET verid = '".$verid."',
			      datetime_verid_sent = now()
			  WHERE email = '".$this->email."'";
		$result = $db->Execute($query);

		if (!$result) {
			$ERROR['insert_error'] = 'Could not create a verification ID';
			return false;
		}

		$email = $this->email;
		$from = $CONFIG['email_from_auth'];
		if ($url) {
			$url = '&url='.$url;
		}

		$verification_link = $CONFIG['full_site'].'?verid='.$verid.$url;
                $subject = "Please confirm that this is really you";
                $message = "Hi ".$this->info['first'].",\r\n\r\nThe page you attempted to access was protected and, for whatever\r\n";
		$message .= "reason, we were unable to verify your identity.  The following \r\n";
                $message .= "url will grant you immediate access to the page you requested:\r\n\r\n";
		$message .= "$verification_link";

                $message = stripslashes($message);
 
                $m = new Mail();
                $m->setTo($email);
                $m->setFrom($from);
                $m->setSubject($subject);
                $m->setTextMessage($message);

                $m->send();

		return true;
	}

	/*****************************************************************
         * Generates a unique 32 character random key.
         *****************************************************************/
        function _generateKey(){
        global $CONFIG;
        global $db;

                $key = md5(uniqid(rand(), true));
                $key = substr($key,0,32);

                #First, check that the unique member_id is actually unique
                $query = "SELECT COUNT(*) as num
                          FROM ".$CONFIG['tbl_members']."
                          WHERE member_id = '$key'";
                $row = $db->GetRow($query);

                if ($row['num'] > 0){
                        $key = $this->_generateKey();
                } else {
                        return $key;
                }
        }

	/*****************************************************************
         * Generates a unique 32 character random key.
         *****************************************************************/
        function _isValidEmail($email){
                if( !eregi('^[a-z0-9]+([_\\.-][a-z0-9]+)*'. '@([a-z0-9]+([\.-][a-z0-9]+))*$',$email) ){
                        return false;
                } else {
                        return true;
                }
        }

	
}
