Hello Nathan,

Thanks for reporting this issue.
We have made a patch to AuthLDAP2.pm that should fix this issue. I have 
attached the new version, and its it also available for download from the 3.6 
patches area.

Please let me know how you go with it.
We apologise for this problem.

Cheers.

On Tue, 15 Jul 2003 05:33 pm, Hugh Irvine wrote:
> Mikey -
>
> Could you take a look at this please?
>
> cheers
>
> Hugh
>
> Begin forwarded message:
> > From: "Radiator" <[EMAIL PROTECTED]>
> > Date: Tue Jul 15, 2003  16:15:40 Australia/Melbourne
> > To: <[EMAIL PROTECTED]>
> > Subject: RE: (RADIATOR) Openldap - Network sockets
> >
> >
> > Update:
> >
> > It looks to be the TLS code somewhere, removing the UseTLS and SSL
> > config makes the problem go away.
> >
> > Regards,
> >
> > Nathan.
> >
> >
> > -----Original Message-----
> > From: Radiator
> > Posted At: Tuesday, 15 July 2003 11:54 AM
> > Posted To: Radiator
> > Conversation: Openldap - Network sockets
> > Subject: (RADIATOR) Openldap - Network sockets
> >
> >
> >
> > I am currently having a problem with Radiator 3.6 and Openldap (2.0.23)
> > where the number of network sockets are exhausted and the LDAP server
> > stops responding with a too many open files error.
> >
> > It looks to me as if the ServerChecksPassword is not set the session is
> > left open (holdserverconnectio is not compatible with openldap it
> > seems)
> > so there are a number of ESTABLISHED sessions (netstat) that are never
> > closed. Could this be a radiator bug ? No unbind ?
> >
> > With the ServerChecksPassword option set, operation is as expected, the
> > sessions are all created individually and closed within appropriate
> > session timeouts.
> >
> > Here is my testing config:
> >
> > <AuthBy LDAP2>
> >     Identifier      ldap_auth
> >     Host hostname
> > #   ServerChecksPassword
> >     Version 3
> >     UseTLS
> >     SSLVerify       none
> >     SSLCAFile
> >     BaseDN  basedn
> >     Scope   sub
> >     UsernameAttr    uid
> >     PasswordAttr    userPassword
> >     Debug 255
> >     NoDefault
> > </AuthBy>
> >
> > There is probably a timeout feature in openldap but I'm sure Radiator
> > should be doing a proper unbind and therefore closing the session.
> >
> > Any ideas? Experiences ?
> >
> > Regards,
> >
> > Nathan Alberti
> > ===
> > Archive at http://www.open.com.au/archives/radiator/
> > Announcements on [EMAIL PROTECTED]
> > To unsubscribe, email '[EMAIL PROTECTED]' with
> > 'unsubscribe radiator' in the body of the message.
> > ===
> > Archive at http://www.open.com.au/archives/radiator/
> > Announcements on [EMAIL PROTECTED]
> > To unsubscribe, email '[EMAIL PROTECTED]' with
> > 'unsubscribe radiator' in the body of the message.
>
> NB: have you included a copy of your configuration file (no secrets),
> together with a trace 4 debug showing what is happening?

-- 
Mike McCauley                               [EMAIL PROTECTED]
Open System Consultants Pty. Ltd            Unix, Perl, Motif, C++, WWW
24 Bateman St Hampton, VIC 3188 Australia   http://www.open.com.au
Phone +61 3 9598-0985                       Fax   +61 3 9598-0955

Radiator: the most portable, flexible and configurable RADIUS server 
anywhere. SQL, proxy, DBM, files, LDAP, NIS+, password, NT, Emerald, 
Platypus, Freeside, TACACS+, PAM, external, Active Directory, EAP, TLS, 
TTLS, PEAP etc on Unix, Windows, MacOS etc.
# AuthLDAP2.pm
#
# Object for handling Authentication via LDAP, using the new
# perl-ldap module Net::LDAP. It replaces AuthLDAP.pm, which
# should not be used for new installations
#
# Author: Mike McCauley ([EMAIL PROTECTED])
# Copyright (C) 1997 Open System Consultants
# $Id: AuthLDAP2.pm,v 1.33 2003/07/16 00:25:05 mikem Exp mikem $

package Radius::AuthLDAP2;
@ISA = qw(Radius::AuthGeneric);
use Radius::AuthGeneric;
use Net::LDAP qw(LDAP_SUCCESS);
use Net::LDAP::Util qw(ldap_error_name);
use strict;

%Radius::AuthLDAP2::ConfigKeywords = 
    ('BaseDN'                => 'string',
     'Host'                  => 'string',
     'Port'                  => 'string',
     'UseSSL'                => 'flag',
     'UseTLS'                => 'flag',
     'AuthDN'                => 'string',
     'AuthPassword'          => 'string',
     'UsernameAttr'          => 'string',
     'PasswordAttr'          => 'string',
     'EncryptedPasswordAttr' => 'string',
     'CheckAttr'             => 'string',
     'ReplyAttr'             => 'string',
     'Debug'                 => 'integer',
     'Timeout'               => 'integer',
     'FailureBackoffTime'    => 'integer',
     'SearchFilter'          => 'string',
     'HoldServerConnection'  => 'flag',
     'ServerChecksPassword'  => 'flag',
     'AuthCheckDN'           => 'string',
     'NoBindBeforeOp'	     => 'flag',
     'Scope'                 => 'string',
     'SSLVerify'             => 'string',
     'SSLCiphers'            => 'string',
     'SSLCAPath'             => 'string',
     'SSLCAFile'             => 'string',
     'SSLClientCert'         => 'string',
     'SSLClientKey'          => 'string',
     'PostSearchHook'        => 'hook',
     'AuthAttrDef'           => 'stringarray',
     'Version'               => 'integer',
     'Deref',                => 'string',
     );

#####################################################################
# Constructs a new handler
sub new
{
    my ($class, @args) = @_;

    my $self = $class->SUPER::new(@args);
    $self->log($main::LOG_WARNING, 
	       "No BaseDN defined for $class at '$main::config_file' line $.")
	if !defined $self->{BaseDN};
    $self->log($main::LOG_WARNING, 
	       "No UsernameAttr defined for $class at '$main::config_file' line $.")
	if !defined $self->{UsernameAttr};
    $self->log($main::LOG_WARNING, 
	       "No PasswordAttr or EncryptedPasswordAttr defined for $class at '$main::config_file' line $.")
	if !defined $self->{PasswordAttr}
           && !defined $self->{EncryptedPasswordAttr}
           && !defined $self->{ServerChecksPassword};

    $self->{Port} = 636 if ($self->{UseSSL} && $self->{Port} == 389);

    # Only one of UseSSL or UseTLS should be specified.  UseSSL will take priority.
    $self->log($main::LOG_WARNING,
		"Both UseSSL and UseTLS defined at '$main::config_file' line $.")
	if defined($self->{UseSSL}) && defined($self->{UseTLS});

    # TLS requires LDAPv3
    $self->{Version} = 3 if ($self->{UseTLS});

    return $self;
}

#####################################################################
# Do per-instance default initialization
sub initialize
{
    my ($self) = @_;

    $self->SUPER::initialize;
    $self->{Host} = 'localhost';
    $self->{UsernameAttr} = 'uid';
#    $self->{Port} = LDAP_PORT;
    $self->{Port} = 389;
    $self->{Scope} = 'sub';
    $self->{SearchFilter} = '(%0=%1)';
    $self->{LDAPRejectEmptyPassword} = 1;
    $self->{SSLVerify} = 'require';    
    $self->{SSLCiphers} = 'ALL';
    $self->{Timeout} = 10;
    $self->{FailureBackoffTime} = 600; # Seconds
    $self->{Version} = 2; # LDAP version
    $self->{Deref} = 'find';
    $self->{SSLCAFile} = ''; # else SSLVerify none causes a crash in Socket/SSL.pm

}

#####################################################################
# reconnect
# Connect or reconnect to the LDAP
# Returns true if there is a viable LDAP connection available
sub reconnect
{
    my ($self) = @_;

    # Some LDAP servers (notably imail) disconnect us after an unbind
    # so we see if we are still connected now
    if ($self->{ld} && !getpeername($self->{ld}->{net_ldap_socket}))
    {
	close($self->{ld}->{net_ldap_socket});
	$self->{ld} = undef;
    }

    return 1 if $self->{ld}; # We are already connected
    return 0 if time < $self->{backoff_until};

    my $host = &Radius::Util::format_special($self->{Host});
    $self->log($main::LOG_INFO, "Connecting to $host, port $self->{Port}");

    $self->{bound} = undef;
    if ($self->{UseSSL})
    {
	require Net::LDAPS;
	my %args;
	$args{clientcert} = $self->{SSLCAClientCert}
	    if defined $self->{SSLCAClientCert};
	$args{clientkey} = $self->{SSLCAClientKey}
	    if defined $self->{SSLCAClientKey};
	$args{cafile} = $self->{SSLCAFile}
	    if defined $self->{SSLCAFile};
	$args{capath} = $self->{SSLCAPath}
	    if defined $self->{SSLCAPath};
	$self->{ld} = new Net::LDAPS
	    ($host,
	     port => Radius::Util::get_port($self->{Port}),
	     verify => $self->{SSLVerify},
	     ciphers => $self->{SSLCiphers},
	     version => $self->{Version},
	     deref => lc $self->{Deref},
	     %args);
    }
    else
    {
	$self->{ld} = new Net::LDAP
	    ($host,
	     port => Radius::Util::get_port($self->{Port}),
	     timeout => $self->{Timeout},
	     version => $self->{Version},
	     deref => lc $self->{Deref});

	if ($self->{ld} && $self->{UseTLS}) 
	{
	    $self->log($main::LOG_DEBUG,"Starting TLS");
	    $IO::Socket::SSL::SSL_Context_obj = undef; # Else get a crash in SSL.pm 2nd time
	    my %args;
	    $args{clientcert} = $self->{SSLCAClientCert} if defined $self->{SSLCAClientCert};
	    $args{clientkey} = $self->{SSLCAClientKey} if defined $self->{SSLCAClientKey};
	    $args{cafile} = $self->{SSLCAFile} if defined $self->{SSLCAFile};
	    $args{capath} = $self->{SSLCAPath} if defined $self->{SSLCAPath};
	    my $result = $self->{ld}->start_tls
		(verify => $self->{SSLVerify},
		 ciphers => $self->{SSLCiphers},
		 %args);
	    if ($result->code) {
		undef $self->{ld};
		$self->log($main::LOG_ERROR,"StartTLS failed: " . $result->error);
	    } else {
		$self->log($main::LOG_INFO,"StartTLS negotiated with cipher mode " . $self->{ld}->cipher);
#		$Radius::AuthLDAP2::tls_started++;
	    }
	}
    }

    if (!$self->{ld})
    {
	$self->{backoff_until} = time + $self->{FailureBackoffTime};
	$self->log($main::LOG_ERR, 
		   "Could not open LDAP connection to $host, port $self->{Port}. Backing off for $self->{FailureBackoffTime} seconds.");
	return 0;
    }

    $self->{ld}->debug($self->{Debug}) if $self->{Debug};

    return 1;  # LDAP is available
}

sub bind
{
    my ($self, $name, $password) = @_;

    return 1 if (   $self->{bound}
		 || $self->{NoBindBeforeOp}); 

    return 0 if time < $self->{backoff_until};

    my $result;
    &Radius::Util::exec_timeout($self->{Timeout},
       sub {
	$self->log($main::LOG_INFO,
	       "Attempting to bind to LDAP server $self->{Host}:$self->{Port})");

	$result = $self->{ld}->bind
	    (defined($name)
	     ? (dn => $name, password => $password)
	     : ()
	     );
    });

    if (!$result || $result->code() != LDAP_SUCCESS)
    {
	my $code = $result ? $result->code() : -1;
	my $errname = ldap_error_name($code);
	$errname = 'LDAP Timeout' if $@ && $@ =~ /timeout/;

	$self->log($main::LOG_ERR, 
		   "Could not bind connection with $name, $password, error: $errname (server $self->{Host}:$self->{Port}).");

	$self->{backoff_until} = time + $self->{FailureBackoffTime};
	$self->log($main::LOG_ERR, 
		   "Backing off from $self->{Host}:$self->{Port} for $self->{FailureBackoffTime} seconds.");

	close($self->{ld}->{net_ldap_socket});
	$self->{ld} = undef;
	return 0;
    }

    return $self->{bound} = 1; # Success
}


#####################################################################
# Check a password for a DN, by attempting to bind with a 
# supplied password. Careful: an empty password will always appear 
# to match, so we reject that case
sub checkPassword
{
    my ($self, $dn, $password) = @_;

    return if $self->{LDAPRejectEmptyPassword} && $password eq '';

    my $result = $self->{ld}->bind
	(dn => $dn, password => $password);
    my $ret = $result && ($result->code() == LDAP_SUCCESS);
    $self->{ld}->unbind;
    return $ret;
}

#####################################################################
sub unbind
{
    my ($self) = @_;

    $self->{ld}->unbind();
    $self->{bound} = undef;
}

#####################################################################
# Find a the named user by looking in the database, and constructing
# User object if we found the named user
# $name is the user name we want
# $p is the current request we are handling
sub findUser
{
    my ($self, $name, $p) = @_;

    # (Re)-connect to the database if necessary, 
    # No reply will be sent to the original requester if we 
    # fail to connect
    return (undef, 1) unless $self->reconnect;

    my $authdn = &Radius::Util::format_special($self->{AuthDN}, $p);
    my $authpassword = &Radius::Util::format_special($self->{AuthPassword}, $p);
    return (undef, 1) unless $self->bind($authdn, $authpassword);
    
    my ($user, @attrs, $got_password);
    # Add password to LDAP request, unless the server will check
    # it later
    if (!$self->{ServerChecksPassword})
    {
	if (defined $self->{EncryptedPasswordAttr})
	{
	    push(@attrs, $self->{EncryptedPasswordAttr});
	}
	else
	{
	    push(@attrs, $self->{PasswordAttr});
	}
    }

    # Continue building LDAP request
    push(@attrs, $self->{CheckAttr}) if defined $self->{CheckAttr};
    push(@attrs, $self->{ReplyAttr}) if defined $self->{ReplyAttr};
	
    # look for all of the new AuthAttr attributes (basically push more
    # attributes onto @attrs
    # my $ldapname;
    my ($ldapname, $attrib, $type, $authattrdef_set);
    foreach $authattrdef_set (@{$self->{AuthAttrDef}}) 
    {
	# my ($attrib, $type) = @{$self->{AuthAttrDef}{$ldapname}};
	($ldapname,$attrib,$type) = split (/,\s*/, $authattrdef_set);
	push(@attrs, $ldapname);
    }

    my $filter = &Radius::Util::format_special
	($self->{SearchFilter}, 
	 $p, undef,
	 $self->{UsernameAttr},
	 $name);
    my $basedn = &Radius::Util::format_special
	($self->{BaseDN}, 
	 $p, undef,
	 $self->{UsernameAttr},
	 $name);
    # We evaluate the search
    # with an alarm for the timeout period
    # pending. If the alarm goes off, the eval will die
    my $result;
    &Radius::Util::exec_timeout($self->{Timeout},
       sub {
	   $result = $self->{ld}->search
	       (base => $basedn,
		scope => $self->{Scope},
		filter => $filter,
		attrs => [EMAIL PROTECTED]);
	   
       });

    # $result is an object of type Net::LDAP::Search
    if (!$result || $result->code() != LDAP_SUCCESS)
    {
	my $code = $result ? $result->code() : -1;
	my $errname = ldap_error_name($code);
	$errname = 'LDAP Timeout' if $@ && $@ =~ /timeout/;
	$self->log($main::LOG_ERR, "ldap search failed with error $errname.", $p);
	if ($errname eq  'LDAP_NO_SUCH_OBJECT')
	{
	    # They are not there
	    return undef;
	}
	elsif ($errname eq  'LDAP_PARAM_ERROR')
	{
	    # Something unpleasant in the username?
	    $self->log($main::LOG_ERR, "LDAP_PARAM_ERROR", $p);
	    return undef;
	}
	else
	{
	    # Any other error probably indicates we lost the connection to 
	    # the database. Make sure we try to reconnect again later.
	    $self->log($main::LOG_ERR, "Disconnecting from LDAP server (server $self->{Host}:$self->{Port}).", $p);
	    close($self->{ld}->{net_ldap_socket});
	    $self->{ld} = undef;
	    return (undef, 1);
	}
    }
	
    # We only use the first returned record
    my $entry = $result->entry(0);
    if ($entry)
    {
	# Get a new User object to return
	$user = new Radius::User;

	my $dn = $entry->dn;
	$self->log($main::LOG_DEBUG, "LDAP got result for $dn", $p);
	
	# We might not be interested in pw check
	$got_password=1
	    if ((!$self->{EncryptedPasswordAttr}) 
		&& (!$self->{PasswordAttr}));

	# Now we have the DN, we can get the server to 
	# check the username if necessary
	if ($self->{ServerChecksPassword})
	{
	    my $auth_check_dn = $dn;
	    $auth_check_dn = &Radius::Util::format_special
		($self->{AuthCheckDN}, $p, undef, $dn)
		if $self->{AuthCheckDN};

	    $got_password = 1;
	    if (!$self->checkPassword($auth_check_dn, $p->decodedPassword()))
	    {
		# LDAP server did not like the password
		$user->get_check->add_attr('Encrypted-Password',
					   "passwordMatchFailedInLDAPServer");
	    }
	}
	my ($attr);
	foreach $attr ($entry->attributes())
	{
	    # This should work for ldap-perl before and after 0.20
	    # vals is now a reference to an array
	    my $vals = $entry->get($attr);

	    # Some LDAP servers (MS) leave trailing NULs
	    map s/\0$//, @$vals;

	    $self->log($main::LOG_DEBUG, "LDAP got $attr: @$vals", $p);

	    # The attributes are not returned in the order we asked 
	    # for them. Bummer. Also the case of the returned 
	    # attribute names does not necessarily match either.
	    # OK so we have to look at each one and see if its one
	    # we expect and need to use
	    $attr = lc $attr;
	    if ($attr eq lc $self->{EncryptedPasswordAttr})
	    {
		$got_password = 1;
		$user->get_check->add_attr('Encrypted-Password',
					   $$vals[0]);
	    }
	    elsif ($attr eq lc $self->{PasswordAttr})
	    {
		$got_password = 1;
		$user->get_check->add_attr('User-Password',
					   $$vals[0]);
	    }
	    elsif ($attr eq lc $self->{CheckAttr})
	    {
		# This is the attribute with check items in it
		$user->get_check->parse(join ',', @$vals);
	    }
	    elsif ($attr eq lc $self->{ReplyAttr})
	    {
		# This is the attribute with reply items in it
		$user->get_reply->parse(join ',', @$vals);
	    }
	    else
            {
		# Perhaps its one of the attributes from AuthAttrDef
		# Based on code contributed by Steven E Ames.
		# my $ldapname;
		my ($ldapname,$attrib,$type, $authattrdef_set);

		
	        # foreach $ldapname (keys %{$self->{AuthAttrDef}})
	        foreach $authattrdef_set (@{$self->{AuthAttrDef}})
	        {
		    ($ldapname,$attrib,$type) = split (/,\s*/, $authattrdef_set);
		    if ($attr eq lc $ldapname)
		    {
			if ($type eq 'check') 
			{
			    if ($attrib eq 'GENERIC')
			    {
				$user->get_check->parse(join ',', @$vals);
			    }
			    else 
			    {
				# Permit alternation from multivalued attrs
				$user->get_check->add_attr($attrib, join('|', @$vals));
			    }
			}
			elsif ($type eq 'reply')
			{
			    if ($attrib eq 'GENERIC')
                            {
				$user->get_reply->parse(join ',', @$vals);
                            }
			    else
			    {
				map {$user->get_reply->add_attr($attrib, $_)}  (@$vals);
			    }
			}
			elsif ($type eq 'request')
			{
			    if ($attrib eq 'GENERIC')
			    {
				$p->parse(join ',', @$vals);
			    }
			    else
			    {
				map {$p->add_attr($attrib, $_)}  (@$vals);
			    }
			}
		    }
		}
	    }
	}

	# Perhaps run a hook to do other things with the LDAP data
        $self->runHook('PostSearchHook', $p, $self, $name, $p, $user, $entry, $p->{rp});
    }
    else
    {
	$self->log($main::LOG_DEBUG, "No entries for $name found in LDAP database", $p);
    }

    # Force disconnection from database. Some LDAP servers
    # dont expect us to try to bind several times on the same
    # TCP connection. Some dont even like us to search several times!
    if (!$self->{HoldServerConnection})
    {
	close($self->{ld}->{net_ldap_socket});
	$self->{ld} = undef;
    }

    if ($user && !$got_password)
    {
	$self->log($main::LOG_ERR, "There was no password attribute found for $name. Check your LDAP database.", $p);
	# Force a rejection
	$user->get_check->add_attr('Encrypted-Password', 'no password attribute in LDAP database');
    }
    return $user;
}
1;




Reply via email to