<?php

/*
** Fishlib - a collection of utilities for db-driven applications in PHP
** Copyright (C) 2002  LTWD, LLC DBA The Madfish Group
**
** This library is free software; you can redistribute it and/or
** modify it under the terms of the GNU Lesser General Public
** License as published by the Free Software Foundation; either
** version 2.1 of the License, or (at your option) any later version.
**
** This library 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
** Lesser General Public License for more details.
**
** You should have received a copy of the GNU Lesser General Public
** License along with this library; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

require_once('fishdb.class.php');
require_once(
'fishargs.class.php');
class 
FishAuth
{

    
// Require a valid username and password from the user.

    // On Apache servers, because this function uses HTTP uses headers 
    // to prompt the user for a name and password, it must be called 
    // before any output - even a blank line or space - is sent to 
    // the browser. the best practice is to call it with output 
    // buffering turned on:

    //    ob_start();
    //    $username = authenticate('My Application','Please log in.');
    //    ob_end_flush();

    // $realm is the label that will appear in the pop-up window that
    // asks for name and address, or as the title of the form.
    // $message is the text that will be displayed if the user hits 
    // the 'Cancel' button in the pop-up

    
function authenticate(
        
$realm 'Restricted Area'
    
$message 'Enter a valid name and password to access this area.'
    
)
    {
        if (!isset(
$_SESSION))
        {
            if (
php_sapi_name() != 'cli')
            {
                
session_start();
            }
            else
            {
                global 
$_SESSION;
                
$_SESSION = array();
            }
        }

    
// check if we can use HTTP authentication - as of now, that means 
    // checking if we are running as an Apache module

    // $_SERVER['PHP_AUTH_USER'] and $_SERVER['PHP_AUTH_PW'] are values 
    // supplied by PHP, corresponding to the user name and password 
    // the user has entered in the pop-up window created by an HTTP 
    // authentication header. If no authentication header has ever 
    // been sent, these variables will be empty. If we are not using 
    // HTTP authentication, the login form will create entries in 
    // the $_POST superglobal with the same names.

        
foreach (array('PHP_AUTH_USER','PHP_AUTH_PW') as $v)
        {
            if (!isset(
$_SESSION[$v]) or empty($_SESSION[$v]))
            {
                if (
FishAuth::http_auth_ok())
                {
                    
$_SESSION[$v] = FishUtil::array_key_value($_SERVER,$v,'');
                }
                else
                {
                    
$_SESSION[$v] = FishUtil::array_key_value($_POST,$v,'');
                }
            }
        }

        
$username NULL;
        if (!empty(
$_SESSION['PHP_AUTH_USER']))
        {
    
// ignore case, even if MySQL has been set to pay attention to it
            
$query "select username from admin
                where password = md5(lower('{$_SESSION['PHP_AUTH_PW']}'))
                    and lower(username) = lower('{$_SESSION['PHP_AUTH_USER']}')
            "
;
            
$result my_query($query);
            if (
$row mysql_fetch_row($result))
            {
                
$username array_shift($row);
            }
            
mysql_free_result($result);

    
// if the query ran but didn't find a match for the user name and password, 
    // $found_user will not be set to anything. if this is so, have the user 
    // try again.  

            
if ($username == NULL)
            { 
                
$message .= <<<EOQ

<li>Could not find entry for username (
{$_SESSION['PHP_AUTH_USER']}) -
please try again.

EOQ;
                unset(
$_SESSION['PHP_AUTH_USER']);
                unset(
$_SESSION['PHP_AUTH_PW']);
            }
        }
        if (
$username == NULL)
        { 
            if (
FishAuth::http_auth_ok())
            {
    
// Send a WWW-Authenticate header, to perform HTTP authentication.
                
Header('WWW-Authenticate: Basic realm="'.$realm.'"');
                
Header('HTTP/1.0 401 Unauthorized');

    
// The user should only see this after hitting the 'Cancel' button
    // in the pop-up form.
                
print $message;
                exit;
            }
            else
            {
    
// Print out an HTML form to obtain a name and password for authentication.

                
if (!empty($message))
                {
                    
$message "<p>$message</p>\n";
                }
                include(
'login.php');
                exit;
            }
        
// should never get here
            
$private_error 'authenticate: error: continued after requesting password';
            
user_error('System error: please contact the system administrator.'E_USER_ERROR);
        }
        else
        {
            print 
"<h5>Logged in as {$_SESSION['PHP_AUTH_USER']}</h5>\n";
        }
        return (
$username);
    }

    function 
http_auth_ok($force=NULL)
    {
        static 
$ok NULL;
        
// check if we can use HTTP authentication - as of now, that means checking
        // if we are running as an Apache module
        // return TRUE or FALSE to force one mode or the other for testing
        
if ($force !== NULL)
        {
            
$ok $force TRUE FALSE;
        }
        if (
$ok === NULL)
        {
            
$ok = (php_sapi_name() == 'apache');
        }
        return 
$ok;
    }


    
// Require a valid username and password from the user.


    // On Apache servers, because this function uses HTTP uses headers 
    // to prompt the user for a name and password, it must be called 
    // before any output - even a blank line or space - is sent to the 
    // browser.  The best practice is to call it with output buffering 
    // turned on:

    //    ob_start();
    //    $username = authenticate('My Application','Please log in.');
    //    ob_end_flush();

    // $realm is the label that will appear in the pop-up window that
    // asks for name and address, or as the title of the form.
    // $message is the text that will be displayed if the user hits 
    // the 'Cancel' button in the pop-up

    
function session_auth()
    {
        static 
$_defaults = array(
            
'realm' => 'Restricted Area'
            
'message' => 'Enter a valid name and password to access this area.'
            
'validate_function' => array('FishAuth','db_validate_login')
        );
        static 
$_simple = array('realm','message');
        
$args func_get_args();
        
$p FishArgs::parse_arguments($args$_simple$_defaults);
        unset(
$p['_defaults']);
        unset(
$p['_simple']);

        
$ok FALSE;
        
$PHP_AUTH_USER NULL;
        
$PHP_AUTH_PW NULL;

        
FishUtil::check_session();
        if (!isset(
$_SESSION))
            global 
$_SESSION;

//        FishError::trace('_SESSION = ', $_SESSION);

        
if (FishAuth::read_login())
        {
//            FishError::trace('after FishAuth::read_login: _SESSION = ', $_SESSION);
            
extract($_SESSIONEXTR_IF_EXISTS);
            
$p['username'] = $PHP_AUTH_USER;
            
$p['password'] = $PHP_AUTH_PW;

            if (
== 1)
            {
                
// just do what i say
            
}
            elseif (
                
is_string($p['validate_function'])
                && 
function_exists($p['validate_function'])
            )
            {
                
// function OK
            
}
            elseif (
                
is_array($p['validate_function'])
                && 
count($p['validate_function']) == 2
                
&& method_exists(
                    
$p['validate_function'][0]
                    , 
$p['validate_function'][1]
                )
            )
            {
                
// object method - hokey doke
            
}
            else
            {
                
$private_error 'No such thing as '
                
var_export($p['validate_function'],TRUE)
                ;
                
user_error('Error: could not validate login - please contact the system administrator'E_USER_ERROR);
                exit;
            }

            
$validate_function $p['validate_function'];
            unset(
$p['validate_function']);
            
$ok call_user_func($validate_function$p);

            if (!
$ok)
            { 
                if (isset(
$p['error_message']))
                {
                    
$p['message'] .= $p['error_message'];
                }
                else
                {
                    
$p['message'] .= <<<EOQ

<li>Could not find entry for username (
{$p['username']}) -
please try again.

EOQ;
                }
            }
        }
        if (!
$ok)
        {
            unset(
$_SESSION['PHP_AUTH_USER']);
//            FishError::trace('after unset of PHP_AUTH_USER: _SESSION=', $_SESSION);
            
unset($_SESSION['PHP_AUTH_PW']);
//            FishError::trace('after unset of PHP_AUTH_PW: _SESSION=', $_SESSION);
            
FishAuth::get_login($p['realm'], $p['message']);
            
session_write_close();
            exit;
        }
        return 
$PHP_AUTH_USER;
    }

    function 
read_login()
    {
        
FishUtil::check_session();
        if (!isset(
$_SESSION))
            global 
$_SESSION;
//        FishError::trace('_SESSION = ', $_SESSION);

    // $_SERVER['PHP_AUTH_USER'] and $_SERVER['PHP_AUTH_PW'] are values 
    // supplied by PHP, corresponding to the user name and password 
    // the user has entered in the pop-up window created by an HTTP 
    // authentication header. If no authentication header has ever been 
    // sent, these variables will be empty. If we are not using HTTP 
    // authentication, the login form will create entries in the 
    // $_POST superglobal with the same names.

        
$result TRUE;
        foreach (array(
'PHP_AUTH_USER','PHP_AUTH_PW') as $v)
        {
            if (!isset(
$_SESSION[$v]) or empty($_SESSION[$v]))
            {
//                FishError::trace('v=',$v,' not set in _SESSION');
                
if (FishAuth::http_auth_ok())
                {
//                    FishError::trace('check for new values in _SERVER');
                    
$check =& $_SERVER;
                }
                else
                {
//                    FishError::trace('check for new values in _POST');
                    
$check =& $_POST;
                }
                if (isset(
$check[$v]))
                {
//                    FishError::trace('found new value for v=', $v, ' - ', $check[$v]);
                    
$_SESSION[$v] = $check[$v];
                }
                else
                {
//                    FishError::trace('did not find new value for v=', $v);
                    
$result FALSE;
                }
            }
            else
            {
//                FishError::trace('v=',$v,' set in _SESSION: ', $_SESSION[$v]);
            
}
        }
        return 
$result;
    }

    function 
logout()
    {
        
$login_fields = array('PHP_AUTH_USER','PHP_AUTH_PW');
        
$args func_get_args();
        foreach (
$args as $arg)
        {
            if (
is_string($arg))
            {
                
$login_fields[] = $arg;
            }
        }
        foreach (
$login_fields as $k)
        {
            if (isset(
$_SESSION) && array_key_exists($k,$_SESSION)) { unset($_SESSION[$k]); }
            if (isset(
$_SERVER) && array_key_exists($k,$_SERVER)) { unset($_SERVER[$k]); }
            if (isset(
$_POST) && array_key_exists($k,$_POST)) { unset($_POST[$k]); }
            if (isset(
$_GET) && array_key_exists($k,$_GET)) { unset($_GET[$k]); }
            if (isset(
$_REQUEST) && array_key_exists($k,$_REQUEST)) { unset($_REQUEST[$k]); }
            if (isset(
$_COOKIE) && array_key_exists($k,$_COOKIE)) { unset($_COOKIE[$k]); }
            if (isset(
$GLOBALS) && array_key_exists($k,$GLOBALS)) { unset($GLOBALS[$k]); }
        }
        
session_write_close();
    }

    function 
get_login($realm='Secure Area',$message='Please login.')
    {
        if (
FishAuth::http_auth_ok())
        {
            
// Send a WWW-Authenticate header, to perform HTTP authentication.
            
Header("WWW-Authenticate: Basic realm=\"$realm\"");
            
Header("HTTP/1.0 401 Unauthorized");

            
// The user should only see this after hitting the 'Cancel' button
            // in the pop-up form.
            
print $message;
            exit;
        }

        
// Print out an HTML form to obtain a name and password for authentication.

        
if (!empty($message))
        {
            
$message "<p>$message</p>\n";
        }
        include(
'login.php');
        exit;
    }

    function 
my_validate_login()
    {
        static 
$_defaults = array(
            
'username' => NULL
            
'password' => NULL
            
'table' => 'admin'
            
'username_field' => 'username'
            
'password_field' => 'password'
            
'crypt_function' => 'md5'
        
);
        static 
$_simple = array('username','password','table');
        
$args func_get_args();
        
$p FishArgs::parse_arguments($args$_simple$_defaults);
        
extract($p);

        
$ok FALSE;
        if (
mysql_ping())
        {
            
$query sprintf(
                
"select 1 as ok from %s where %s = '%s' and %s = %s('%s')"
                
$table$username_field$username$password_field
                
$crypt_function$password
            
);
            
$result = @mysql_query($query);
            if (
$result)
            {
                list(
$ok) = mysql_fetch_row($result);
                
mysql_free_result($result);
            }
        }
        return 
$ok;
    }

    function 
db_validate_login()
    {
        static 
$_defaults = array(
            
'username' => NULL
            
'password' => NULL
            
'table' => 'admin'
            
'username_field' => 'username'
            
'password_field' => 'password'
            
'crypt_function' => 'md5'
            
'extra' => NULL
        
);
        static 
$_simple = array('username','password','table');
        
extract($_defaults);
        
$args func_get_args();
        
$p FishArgs::parse_arguments($args$_simple$_defaults);
        
extract($pEXTR_IF_EXISTS);

        
$ok FALSE;
        
$dbh FishDB::db_connect();
        if (
$dbh)
        {
            
$query 'select 1 as ok from ! where ! = ?  and ! = !(?)';
            
$bind = array($table$username_field$username$password_field
                
$crypt_function$password
            
);
            if (!empty(
$extra))
            {
                
$query .= ' and !';
                
$bind[] = $extra;
            }
            
            
$ok = (bool)$dbh->getOne($query,$bind);
        }
        return 
$ok;
    }
}
?>