Introducing the RUHA Lib

By popular request—or at least by Harry’s request—the Really Ugly Hack Authentication Library.

First a little preface:
This library was designed a couple of years ago to make up for the lack of NTLM authentication for apache. We—my system administrator and myself—tried several flavors of “mod_ntlm” projects for apache. We were not able to successfully compile any of these against Linux (let alone the HP-UX 10.20 production environment :( ). We tried to contact several of the maintainers without success, and so gave up the quest. This solution probably does not have very wide applicability, but we already had both Unix/Apache and Windows/IIS servers up and running, plus our staging process required users to have IE which supports NTLM authentication out of the box (as does Mozilla and Firefox now) so it did suit my needs.

BTW, if anyone knows of a working mod_ntlm module for apache now, please comment :).

My early background in web programming (prior to PHP enlightenment) was in ASP on IIS. Under this scenario, turning on security on the directory allowed you to pull the NT user name from the server variable…very handy :). Knowing this was possible, I devised a scheme were a PHP script on Apache would redirect to an ASP script on another IIS server in a NTLM protected directory, which would return the user id encrypted for the PHP script to decrypt. This scenario worked for the most part, but occasionally the user name got garbled in decryption, so I resolved to try this trick multiple times, and if I got the same result back twice in a row, I considered it golden.

Visually, this is how it looks:

follow the bouncing ball

Here then are the scripts to make this hack work (please remember this code is several years old, procedural in nature and lack any sort of layering, I have not spent that much time cleaning it up for you-please be kind in your comments 😉 ):

A function call to wrap all of this code, this would be in “the_lib.php” from my previous post:

<?php
/**
 *    integrate with auth.php for scrty
 *
 *        NOTE: must start script calling this function with session_start();
 *
 *    @author     Jason E. Sweat
 *    @since     2002-05-03
 *    @param        string    $appl_idftn                 the name of the variable to register
 *    @param        boolean    $deny_accs        optional     exit the script if scrty check fails, default true
 *    @param        string    $redir_parms    optional     parameters for the redirect url, default nothing
 *    @param        string    $redir_url        optional     redirection url, default php_self()
 *    @global     string    $scrty                         will be set to the contents of $_SESSION['scrty']
 *    @global     string    $_scrty_appl_idftn            will be set to the contents of $_SESSION['scrty_data']['sctry_appl']
 *
 */
function use_scrty(
     $appl_idftn    = 'APPL_IDFTN_NOT_SET'
    ,$deny_accs        = true
    ,$redir_parms    = ''
    ,$redir_url        = 'REDIR_URL_NOT_SET'    )
{
    global $scrty;
    global $_scrty_appl_idftn;
    
    $scrty = (array_key_exists('scrty',$_SESSION)) ? $_SESSION['scrty'] : NULL;
    if (array_key_exists('scrty_data', $_SESSION)) {
        $_scrty_appl_idftn = (array_key_exists('scrty_appl',$_SESSION['scrty_data'])) ? $_SESSION['scrty_data']['scrty_appl'] : NULL;
    } else {
        $_scrty_appl_idftn = NULL;
    }
    
    if ('APPL_IDFTN_NOT_SET' == $appl_idftn) {
        die('Must pass Application Identification to use_scrty().');
    }
    if ('REDIR_URL_NOT_SET' == $redir_url) {
        $redir_url = phpself();
    }
    
    $s_host = $_SERVER['SERVER_NAME'];
    
    if (!$scrty || !($_scrty_appl_idftn == $appl_idftn) ) {
        $loc = "location: http://$s_host/auth/auth.php?appl=$appl_idftn&redirurl=";
        $loc .= urlencode($redir_url.$redir_parms);
        header($loc);
    } else {
        if ($deny_accs && !$scrty['appl_acss']) {
            $die_msg = ($scrty['deny_msg']) 
                ? $scrty['deny_msg']
                : "<h1 style='text-align: center'>You do not have access to this application.</h1>n<!-- $appl_idftn -->n";
            die( $die_msg );
        }
    }
}
</>
?>

Next, you have to have this auth.php script in a /auth/ directory of your web server:

<?php

/**
 *    @author     Jason E. Sweat
 *    @purpose     integrate NT security with SCRTY for PHP web apps 
 *    @created     2002-03-21
 *    @revision    $Revision: 1.4 $
 */
session_start();
 
//general script files
require_once('the_lib.php');  //has the "register" function to extract $_REQUEST vars
                // and sql_format to do some string replacement
require_once('auth_sql.php'); //just some SQL statement 
//rc4 encryption class file
require_once('rc4crypt/class.rc4crypt.php');  //will post this one below

//database related includes
require_once('adodb/adodb.inc.php');
require_once('dblogin.php');  //containts connection parameters used below

//set up unix environment variables (required for oracle connectivity)
putenv('ORACLE_SID=prdcrm');
putenv('ORACLE_HOME=/pkg/prdcrm/oracle');
//required for Oracle version below 8.1.5
putenv('NLS_LANG=');

// define modes
define('SCRTY_START',1);
define('SCRTY_AUTH',2);
define('SCRTY_ERROR',99);
define('AUTH_TIMEOUT',30);
define('AUTH_MAXTRIES',30);

$dbc = 'prdgenl';
$conn = &ADONewConnection('oci8');
//$conn->debug=true;
$conn->PConnect('',${$dbc}['uid'],${$dbc}['pwd'],${$dbc}['db']);
$ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
$ntauth_url = 'http://youriis.server.example.com/auth/auth.asp';

$scrty_data = $_SESSION['scrty_data'];
$mode = $scrty_data['scrty_mode'];
if (!$mode || SCRTY_ERROR == $mode) {
    $mode = SCRTY_START;
}

if (!scrty_data) {
    $scrty_data = array();
}

switch ($mode) {
    case SCRTY_START:
        $referer = $_SERVER['HTTP_REFERER'];
        // check if scrty appl is valid
        $redirurl = register('redirurl');
        if (!$redirurl) {
            show_error ("I don't know where to go when I am done!");
            show_error("Please contact the maintainer of $referer for proper configuration.");
            exit;
        } else {
            $scrty_data['redirurl'] = $redirurl;
        }
        $scrty_appl = register('appl');
        if (!$scrty_appl) {
            show_error("Application ($referer) is not configured correctly,");
            show_error('contact application developer.');
            exit;
        } else {
            $sql = sql_format($auth_sql['check_scrty_appl'], array('APPL' => $scrty_appl));
            $rs = $conn->Execute($sql);
            $row = $rs->fields;
            if (!1==$row['CNT']) {
                show_error("$scrty_appl is not a valid application.");
                show_error("Please contact the maintainer of $referer for proper configuration.");
            } else { // scrty_appl is okay, proceed to user authentication 
                $scrty_data['scrty_appl'] = $scrty_appl;
                $scrty_data['scrty_appl'] = $scrty_appl;
                $scrty_data['scrty_mode'] = SCRTY_AUTH;
                $scrty_data['scrty_tries'] = 1;
                $scrty_data['scrty_last_uid'] = 'not yet set';
                $_SESSION['scrty_data'] = $scrty_data;
                $skey = substr(md5(microtime()),0,15).mktime(); //strftime('%Y%m%d%H%M%S').
                header('Location: '.$ntauth_url.'?skey='.urlencode($skey));
                exit;
            }
        }
        // set mode to auth and start identifying NT user
        break;
    case SCRTY_AUTH:
        // check if current decrypted NT user is same as last time
        $uid = register('uid');
        $rc4 = new rc4crypt;
        if (!$uid) {
            show_error('Something funny is going on, I did not get your encrypted user id.');
            exit;
        }
        $skey = register('skey');
        $diff = mktime() - substr($skey,15,10);
        if ($diff > AUTH_TIMEOUT) {
            show_error('Are you tring something funny?  Please try to log in again.');
            exit;
        }
        if (strlen($skey)) {
            while (strlen($skeytmp) < strlen($rc4->NT_IIS_Auth_key)) {
                $skeytmp .= $skey;
            }
        } else {
            $skeytmp = '00000000000000000000000 ';
        }
        $usekey = urldecode($rc4->endecrypt($skeytmp, $rc4->NT_IIS_Auth_key, ''));
        $output = $rc4->endecrypt($usekey, $uid, 'de');
        if ( !is_valid_login($output) || $output <> $scrty_data['scrty_last_uid']) {
            $scrty_data['scrty_tries']++;
            $scrty_data['scrty_last_uid'] = $output;
            if ($scrty_data['scrty_tries'] > AUTH_MAXTRIES) {
                show_error('Pulling the andon cord.');
                print '<pre>';
                var_dump($_SESSION);
                exit;
            }
            $skey = substr(md5(microtime()),0,15).mktime(); //strftime('%Y%m%d%H%M%S').
            $_SESSION['scrty_data'] = $scrty_data;
            header('Location: '.$ntauth_url.'?skey='.urlencode($skey));
            exit;
        } else {// yes -> do scrty load stuff
            $scrty_data['scrty_mode'] = SCRTY_START;
            $_SESSION['scrty_data'] = $scrty_data;
            scrty_load_info($output);
        }
        break;
    default:
        // can't get here normally, should abort with error
        break;
}

/**
 *    @function     scrty_deny
 *    @author     Jason E. Sweat
 *    @purpose     load scrty data into the session and redirect to the application page
 *    @created     2002-03-21
 *    @parm        string        $scrty        the $scrty array, passed by reference
 *    @parm        string        $login        the login from NT_AUTH_USER
 *
 */
function scrty_deny(&$scrty, $login)
{
    global $scrty_data;
    global $conn;
    global $auth_sql;
    
    $parms = array(
         'LOGIN'     =>    $login
        ,'APPL'        =>    $scrty_data['scrty_appl']
        );
        
    $scrty['deny_msg'] = < <<EOS
<html>
<head>
<title>Access Denied - {$scrty_data['scrty_appl']}</title>
</head>
<body>
<div style="text-align: center"><h1 style="color: red">Access Denied</h1>
EOS;

    if ($scrty['valid_login']) {
        //query for name info from all pers view
        $sql = sql_format($auth_sql['fetch_scrty_info'], $parms);
        $rs = $conn->Execute($sql);
        $row = $rs->fields;
        $scrty['deny_msg'] .= '<p style="color: blue">'.$row['NAME_FIRST'].' '.$row['NAME_LAST']."n";
    }
    // fetch admin users for appl
    $sql = sql_format($auth_sql['admin_scrty_info'], $parms);
    $rs = $conn->Execute($sql);
    $appl_desc = $rs->fields['APPL_DESC'];
    $scrty['deny_msg'] .= < <<EOS
<table border="0" cellpadding="5" cellspacing="0">
<tr>
    <td colspan="2">
        You do not have access to the $appl_desc application.<br>
        If you think you should have access to this site, you might try contacting:
    </td>
</tr>
<tr><th width="50%">Contact</th><th width="50%">Phone</th></tr>
EOS;
    while ($rs && !$rs->EOF) {
        $row = $rs->fields;
        if ($row['EMAIL_ADDR']) {
            $email_start = '<a href="mailto:'.$row['EMAIL_ADDR'].'?subject=Access+to+'.$scrty_data['scrty_appl'].'">';
            $email_end = '</a>';
        } else {
            $email_start = '';
            $email_end = '';
        }
        $scrty['deny_msg'] .= < <<EOS
        <tr align="center">
            <td>$email_start{$row['NAME_FIRST']} {$row['NAME_LAST']}$email_end</td>
            <td>{$row['SITE_PHONE_NMBR']}</td>
        
EOS;
        $rs->MoveNext();
    }
    $scrty['deny_msg'] .= "n</></></div>n</body>n";
}

/**
 *    @function     scrty_load_info
 *    @author     Jason E. Sweat
 *    @purpose     load scrty data into the session and redirect to the application page
 *    @created     2002-03-21
 *    @parm        string        $login        the DOMAINlogin from NT_AUTH_USER
 *
 */
function scrty_load_info($login)
{
    global $scrty_data;
    global $conn;
    global $auth_sql;

    $login_info = split_login($login);
    $scrty_info['login_info'] = $login_info;
    
    $parms = array(
        'LOGIN'     =>    $login_info['login']
        ,'APPL'        =>    $scrty_data['scrty_appl']
        );
    $sql = sql_format($auth_sql['check_login_idftn'], $parms);
    $rs = $conn->Execute($sql);
    $row = $rs->fields;
    if (!1==$row['CNT']) { // login not found, bail
        $scrty['valid_login'] = false;
        $scrty['appl_acss'] = false;
        scrty_deny($scrty,$login_info['login']);
    } else { // is a valid login, continue
        $scrty['valid_login'] = true;
        
        $sql = sql_format($auth_sql['check_appl_acss'], $parms);
        $rs = $conn->Execute($sql);
        $row = $rs->fields;
        if (!1==$row['CNT']) { // login does not have access
            $scrty['appl_acss'] = false;
            scrty_deny($scrty,$login_info['login']);
        } else {
            $scrty['appl_acss'] = true;
            // bump login counts
            $sql = sql_format($auth_sql['upd_appl_acss'], $parms);
            $rs = $conn->Execute($sql);
        }
        $sql = sql_format($auth_sql['fetch_scrty_info'], $parms);
        $rs = $conn->Execute($sql);
        $row = $rs->fields;
        $scrty['info'] = $row;
    }
    $_SESSION['scrty_data'] = $scrty_data;
    $_SESSION['scrty'] = $scrty;
    header('Location: '.$scrty_data['redirurl']);
    exit;
}

/**
 *    @function     show_error
 *    @author     Jason E. Sweat
 *    @purpose     show an error message in BIG RED LETTERS
 *    @created     2002-03-21
 *    @parm        string        $msg        the error message to display
 *
 */
function show_error($msg)
{
    global $scrty_data;
    global $_SESSION;
    
    $scrty_data['scrty_mode'] = SCRTY_ERROR;
    $_SESSION['scrty_data'] = $scrty_data;
    print '<h1><font color="red">'. htmlentities($msg) .'</font></h1>';
}

/**
 *    @function     split_login
 *    @author     Jason E. Sweat
 *    @purpose     Return an array of the domain and login, assumes a valid login
 *    @created     2002-03-21
 *    @parm        string        $login        the DOMAINlogin from NT_AUTH_USER
 *    @return        array
 *
 */
function split_login($login)
{
    $slash = '';
    $check = split("[$slash]",$login);
    $out['domain'] = $check[0];
    $out['login'] = $check[1];
    return $out;
}

/**
 *    @function     is_valid_login
 *    @author     Jason E. Sweat
 *    @purpose     Return true or false depending on whether the value appears to be a valid
 *                domainlogin.  The logic uses the split function to created an array of elements
 *                one for each of the  in the string, then check to make sure there are exactly
 *                two elements and each of the elements is a populated string.
 *    @created     2002-03-21
 *    @parm        string        $login        the DOMAINlogin from NT_AUTH_USER
 *    @return        boolean
 *
 */
function is_valid_login($login)
{
    $slash = '';
    $check = split("[$slash]",$login);
    if ( 2 == count($check)) {
        if (strlen($check[0]) && strlen($check[1])) {
            return true;
        }
    }
    return false;
}
</>
?>

here is the rc4 class (class.rc4crypt.php):

<?php
Made By Mukul Sabharwal [mukulsabharwal@yahoo.com]
// http://www.devhome.net/php/
// On October 21, 2000
// Updated February 24, 2001
// Now passes RC4 Vector Harness

class rc4crypt {
var $NT_IIS_Auth_key = 'shared_secret_key_between_asp_and_php';

    function endecrypt ($pwd, $data, $case='') {

        if ($case == 'de') {

            $data = urldecode($data);

        }

        $key[] = "";
        $box[] = "";
        $temp_swap = "";
        $pwd_length = 0;

        $pwd_length = strlen($pwd);

        for ($i = 0; $i < = 255; $i++) {

            $key[$i] = ord(substr($pwd, ($i % $pwd_length), 1));
            $box[$i] = $i;

        }

        $x = 0;

        for ($i = 0; $i <= 255; $i++) {

            $x = ($x + $box[$i] + $key[$i]) % 256;
            $temp_swap = $box[$i];

            $box[$i] = $box[$x];
            $box[$x] = $temp_swap;

        }

        $temp = "";
        $k = "";

        $cipherby = "";
        $cipher = "";

        $a = 0;
        $j = 0;

        for ($i = 0; $i < strlen($data); $i++) {

            $a = ($a + 1) % 256;
            $j = ($j + $box[$a]) % 256;

            $temp = $box[$a];
            $box[$a] = $box[$j];

            $box[$j] = $temp;

            $k = $box[(($box[$a] + $box[$j]) % 256)];
            $cipherby = ord(substr($data, $i, 1)) ^ $k;

            $cipher .= chr($cipherby);

        }

        if ($case == 'de') {

            $cipher = urldecode(urlencode($cipher));

        } else {

            $cipher = urlencode($cipher);

        }

        return $cipher;

    }

}

?>

And now the ASP side of the equation, auth.asp:

< %@ Language=VBScript %>
< % option explicit %>

< %

' auth.asp 
' requires sid post variable
' returns hash of sid, server.auth_user and private key
dim sSID, sReDirURL, sNTUser, sKey, i, sBuffer, sSIDTmp, sUseKey

sSID = Request.Form("skey") & Request.QueryString("skey")
sReDirURL = Request.Form("redirurl") & Request.QueryString("redirurl")
sNTUser = Request.ServerVariables("AUTH_USER")
sKey = "shared_secret_key_between_asp_and_php"

if len(sReDirURL) = 0 then
	sReDirURL = "http://yourapache.server.example.com/auth/auth.php"
end if

If len(sSID) > 0 Then
	While len(sSIDTmp) < len(sKey) 
		sSIDTmp = sSIDTmp & sSID
	Wend
Else
	sSIDTmp = "00000000000000000000000"
End If

'For i = 1 to len(sKey)
'	sUseKey = sUseKey & Chr( (Asc(Mid(sSIDTmp,i,1))+1) Xor (Asc(Mid(sKey,i,1))+1) )
'Next

sUseKey = EnDeCrypt(sKey,sSIDTmp)

' this works, requires anonymous access disabled on the directory 
'Response.Write "You are: " & sNTUser

sBuffer = EnDeCrypt(sNTUser,sUseKey)
Response.Redirect(sReDirURL & "?skey=" & sSID & "&uid=" & server.URLEncode(sBuffer)) '&"&uk="&server.URLEncode(sUseKey))
%>

and rc4.inc.asp:

:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
'::: :::
'::: This script performs 'RC4' Stream Encryption :::
'::: (Based on what is widely thought to be RSA's RC4 :::
'::: algorithm. It produces output streams that are identical :::
'::: to the commercial products) :::
'::: :::
'::: This script is Copyright