Authentication Server

Zeus Server provides a high-performance API to allow third parties to completely customize the access control performed by the server. The API allows the server to delegate its access control policies for portions of its document tree to a separate authentication server. The interface utilizes the TCP/IP socket abstraction, allowing the authentication server to be running on a physically different machine from the server, and multiple Zeus Servers to share a centralized authentication server. It also allows the authentication server implementation to be in any programming language which can interact with sockets.

Sites running online subscription magazines, for example, have very complicated access control policies, requiring external database lookups on every request. On receiving a request, Zeus Server can be configured to connect to an authentication server to check whether the request should be satisfied or requires a password etc. The authentication server can do whatever is required to validate the client, for example perform some SQL query into a central database on a mainframe where all of the subscription records are kept. Such a design allows for complete flexibility.

Simple Example

Here is an example of a authentication server.This is a simple authentication server written in Perl, and demonstrates how to interact with the authentication API. It receives requests from Zeus Server for authentication information, and checks the user details supplied in a separate text-file containing its authentication database.

Authentication daemon protocol:

Input:

Hostname: (DNS name|IP address)\r\n
URL: url-requested\r\n
Method: (GET|POST...)\r\n
Password: (NULL|userid:password)\r\n
Cookie: (NULL|cookie)\r\n
\r\n

Output:

NO : forbidden
PASSWORD : Password required, or wrong password supplied
YES : access granted

Presented here is a small example of an authentication daemon (herein called authd) capable of interacting with the Zeus Server authentication API.

Upon startup, the authd requires a port number to run on, and the filename of the access file to use. The access file is a simple text file which contains information of the following format:

url:userid:password

Where url is the request sent from the client (eg. /~foobar/private.html) and userid/password are the required input from the client for authorization.

Upon receiving a complete request from Zeus Server, the authd opens the access file, and scans it to see if the requested URL has any access restrictions. If no restrictions are found, the authd allows access, otherwise it compares the userid/password pair supplied by the client with that contained in the access file to determine whether access should be granted.

#!/usr/bin/perl

require 5.001;
use Socket;
use POSIX;

# authorize(hostname,url,method,password,cookie)
# Simple example of an authorize function which just looks up the URL in the
# access file, and if present checks authorization, otherwise grants access.
sub authorize {
    my $url      = $_[1];
    my $password = $_[3];
    my $cookie   = $_[4];

    open(FD,$accessfile) || return "NO";	# unable to open file
    {
       local $/ = "\n";
       while(<FD>) {
          my $furl,$fuid,$fpwd;
          ($furl,$fuid,$fpwd) = split(/:/);
          chomp $fpwd;
          if($url =~ /$furl.*/) {	    # access restricted on this url
             close(FD);
             if ($password eq "$fuid:$fpwd") { return "YES" }
             return "PASSWORD";	# Need correct password
          }
       }
       close(FD);
    }
    return "YES";	# access not restricted, grant access
}

# complete(string)
# Determines whether a complete request has been recieved yet.  Returns
# non-zero if so.
sub complete { return ($_[0] =~ /\r\n\r\n/) }

# process(string)
# Takes a complete request, and returns the data to output.
sub process {
    my $buf = $_[0];
    $buf =~ m/Hostname:\ (.*)\r\n
              URL:\      (.*)\r\n
              Method:\   (.*)\r\n
              Password:\ (.*)\r\n
              Cookie:\   (.*)\r\n/x;
    return( authorize($1,$2,$3,$4,$5) );
}

# ----------------------------------------------------------------------------
# Server code

if( $#ARGV!=1 ) {
    print "Usage: authd port accessfile\n";
    exit 1;
}
$port = shift; $accessfile = shift;

my $proto = getprotobyname('tcp');
socket(SERVER, PF_INET, SOCK_STREAM, $proto)        || die "socket: $!";
setsockopt(SERVER, SOL_SOCKET, SO_REUSEADDR, 1)     || die "setsockopt: $!";

$sockaddr = 'S n a4 x8';
$this = pack($sockaddr, AF_INET, $port, "\0\0\0\0");
bind(SERVER, $this)                                 || die "bind: $!";
listen(SERVER,5)                                    || die "listen: $!";
print "authd server started on port $port using accessfile $accessfile\n";

$child = fork;
if ($child < 0) {           # fork failed
    die "error on fork: $!";
} elsif ($child > 0) {      # parent
    exit(0);
} else {                    # child
    if (!setsid()) {
	die "failed setsid: $!";
    }
}


$rin = ''; vec($rin,fileno(SERVER),1) = 1;
$win = '';
$ein = '';

# Main program loop
while(1) {
    $nfound = select($rout=$rin,$wout=$win,$eout=$ein, undef);

    if(vec($rout,fileno(SERVER),1)) {       # new connection
        my $NS = FileHandle->new();
	if(accept($NS,SERVER)) {
	    vec($rin,fileno($NS),1) = 1;
	    vec($ein,fileno($NS),1) = 1;
	    $input[fileno($NS)] = $output[fileno($NS)] = '';
	    $fd2file[fileno($NS)] = $NS;
	    push ((@clients),fileno($NS));
	}
    }
    else {
	# look through read/write/except bits for clients
	@cl = @clients;
	while($fd = shift @cl) {
	    if(vec($eout,$fd,1)) {      # Exception on $fd
		&killclient($fd);
	    }
	    if(vec($wout,$fd,1)) {      # Write on $fd
		local($len) = length($output[$fd]);
		if($len) {          	# data left to write
		    $w=syswrite $fd2file[$fd],$output[$fd],$len;
		    if(!defined($w)) { &killclient($fd); }
		    else { $output[$fd] = substr($output[$fd],$w,$len-$w); }
		}
		else { &killclient($fd); }    # finished
	    }
	    if(vec($rout,$fd,1)) {      # Read on $fd
		$r=sysread $fd2file[$fd],$input[$fd],128,length($input[$fd]);
		if(!defined($r) || !$r) { &killclient($fd); }
		else { &parse($fd); }
	    }
	}
    }
}

# Removes a client of given fd from the system
sub killclient {
    local($fd) = $_[0];
    local(@cl);
    local($i);

    vec($rin,$fd,1) = 0;
    vec($win,$fd,1) = 0;
    vec($ein,$fd,1) = 0;
    $input[$fd] = $output[$fd] = '';
    close $fd2file[$fd];
    while($i = shift @clients) {
	if($i != $fd) { push ((@cl),$i); }
    }
    @clients = @cl;
}

# Determines whether a complete request on an fd has been recieved, and if so
# processes it
sub parse {
    local($fd) = $_[0];
    if(&complete($input[$fd])) {      # got a complete request
	vec($rin,$fd,1) = 0;
	vec($win,$fd,1) = 1;
	$output[$fd] = &process($input[$fd]);
    }
}

FILEHANDLE:
{
    use strict;
    require FileHandle;  # make sure real one is loaded
    package FileHandle;

    sub new {
	my $self = shift;
	my $class = ref($self) || $self;  # for inheritance
	return bless(&_genfh, $class);
    }

    sub DESTROY {
        my $self = shift;
        if (defined fileno $self) {
            close $self;
        }
    }

    ##########
    #         internal only
    ##########
    sub _genfh {
        no strict 'refs';
        local *{'FileHandle::DEMI_ANON_GLOB'};
        return \delete $FileHandle::{DEMI_ANON_GLOB};
    }
    1;
}
Content Manager [Administrator] 16 December 2005  Permalink  
Download Free Trial

Recent Articles

Other Resources



www.zeus.com