#
# Copyright 2002-2003 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#
#ident   "@(#)AuthCookieHandler.pm 1.5     03/02/25 SMI"
#
# Subclass of AuthCookie to authenticate the user to the SunPlex Manager
# Handles cookie and basic authentication
#

package Cluster::AuthCookieHandler;
use strict;
use Apache;
use Apache::Constants qw(:common);
use Apache::AuthCookieMod;
use Cluster::CookieStorage;
use Cluster::Common;
use Cluster::RBAC;
use MIME::Base64;
use vars qw($VERSION @ISA);

$VERSION = substr(q$Revision: 1.1 $, 10);
@ISA = qw(Apache::AuthCookieMod);

# Hold basic authentication passwords
my %passwd_cache;

# Cluster objects
my $rbac = new Cluster::RBAC;
my $cluster = new Cluster::Common;

# Cluster running mode definitions must match those in pam_verify.c
my $INSTALLMODE = "installmode";
my $CLUSTERMODE = "clustermode";

# Authentication routine: does basic (Base64) authentication for
# communication between nodes, and otherwise passes request to
# AuthCookie superclass.

sub authenticate($$) {
        my $self = shift;
	my ($r) = @_;
	my ($auth) = $r->header_in('Authorization');
	if (defined($auth) && $auth =~ /^Base64 /) {
	    # Authenticate using the Base64 authorization
	    # This is used for inter-node communication
	    $auth =~ s/^Base64 //;
	    my @credentials;
	    @credentials = map {MIME::Base64::decode($_)} split(/:/, $auth);
	    return $self->pam_verify(@credentials);
	}
	return $self->SUPER::authenticate(@_);
}

# Test if a credential is valid
# Returns either a new cookie, or an error "authentication" or "authorization"

sub authen_cred ($$\@) {
    my $self = shift;
    my $r = shift;
    my @creds = @_;

    # Check with pam_verify if the user/password is okay.
    my $mode = $self->get_cluster_mode();
    my ($user, $user_pw, $role, $role_pw) = @creds;
    open(OUT, "| /usr/bin/pfexec /opt/SUNWscvw/sbin/pam_verify $mode $user $role") ||
	return DECLINED;
    print OUT "$user_pw\n";
    print OUT "$role_pw\n";
    close(OUT);
    my $ret = ($?>>8);
    if ($ret == 0) {
	# Logged in successfully

	# Generate a cookie
	my ($cookie) = &Cluster::CookieStorage::create_cookie($user, $user_pw, $role, $role_pw);

	# Store info in the cookie cache
	&Cluster::CookieStorage::set_cookie($cookie, 0, $user, $user_pw, $role, $role_pw);

	$r->subprocess_env('AuthInfo', 'success');
	$r->subprocess_env('AuthCookie', $cookie);

	return $cookie;
    } else {
	if ($ret == 1) {
	    return "authorization"; # Error return
	} elsif ($ret == 2) {
	    return "authentication"; # Error return
        } elsif ($ret == 3) {
	    return "role_authorization"; # Error return
        } elsif ($ret == 4) {
	    return "role_authentication"; # Error return
        }
    }
}

# Test if a session key (i.e. cookie) is valid.
# Returns userid, role (userid), or undef for a regular login,
# role login, or invalid login respectively

sub authen_ses_key ($$$) {
    my $self = shift;
    my $r = shift;
    my($cookie) = shift;

    # Authenticate use here...

    if ($cookie eq "authorization" || $cookie eq "authentication" ||
		$cookie eq "logout") {
	    # Exit if cookie indicates status of previous request
	    $r->subprocess_env('AuthInfo', $cookie);
	    return undef;
    }

    my (@entry) = &Cluster::CookieStorage::get_cookie($cookie);

    if (defined($entry[0])) {
	$r->subprocess_env('AuthCookie', $cookie);
	if (defined($entry[2]) && $entry[2] ne "") {  # role
	    return $entry[2]." (".$entry[0].")"; # role (user)
	} else {
	    return $entry[0]; # userid
	}
    } else {
	return undef;
    }
}

# Get the roles for the given user
sub get_roles($$) {
	my ($self, $user) = @_;
	return ($rbac->get_roles($user));
}

# Get the credentials stored in the cookie
sub get_creds ($$$) {
	my ($self, $r, $cookie) = @_;
	my (@entry) = &Cluster::CookieStorage::get_cookie($cookie);
	return @entry;
}

# Check to see if the user has any cluster read auths
sub check_read_auths() {
	my ($self, $user) = @_;
	return $rbac->check_read_auths($user);
}

# Logout routine: deletes the cookie and then passes the operation
# to the AuthCookie superclass.

sub logout {
    my ($self) = shift;
    &Cluster::CookieStorage::delete_cookie($ENV{"AuthCookie"});
    $self->SUPER::logout(@_);
}

# Verify a user and/or role login via PAM
# Cache results so we don't look up every time
# Return OK, AUTH_REQUIRED (authentication error),
# FORBIDDEN (authorization error), or SERVER_ERROR (internal error)
sub pam_verify {
    my ($self, $user, $user_pw, $role, $role_pw) = @_;

    # See if we've cached this user/role/password
    if ($passwd_cache{$user+":"+$role} eq
	    crypt($user_pw, $user) + ":" + crypt($role_pw, $role)) {
	return OK;
    }
    # Check with pam_verify if the user/role/password is okay.
    my $mode = $self->get_cluster_mode();
    open(OUT, "| /usr/bin/pfexec /opt/SUNWscvw/sbin/pam_verify $mode $user $role") ||
	return DECLINED;
    print OUT "$user_pw\n";
    print OUT "$role_pw\n";
    close(OUT);
    if ($? == 0) {
	# Ok, so remember in the cache.
	$passwd_cache{$user + ":" + $role} = crypt($user_pw, $user) +
	 ":" + crypt($role_pw, $role);
	return OK;
    } else {
	return DECLINED;
    }
}

# Get the cluster running mode to pass to pam_verify
sub get_cluster_mode() {
	my ($self) = @_;
	if ($cluster->is_installed_cluster() == 0) {
		return $CLUSTERMODE;
	} else {
		return $INSTALLMODE;
	}
}

# Return success for module load
1;
