jarich has asked for the wisdom of the Perl Monks concerning the following question:

I've run into an interesting problem and hope that the Perl monks here might be able to give me some advice. My current job at work is to alter a very successful student service site to play nicely with a portal we've just bought.

Currently we require the students to submit their PIN with every change to their details and hope that that can be avoided with the portal. The first time that the student comes in from the portal they'll hit a basic auth page and submit their username and password. From then on the portal will submit their details to my site and no further PIN or password will be needed.

Unfortunately their username will not be their student number, so I need to make an LDAP query to our LDAP server to get their student number based on their username. This is easy, I'm using perl-ldap version 0.26. (I did try perldap but had too much trouble getting it to install on our alpha machine).

The machine that our (Forte) database (with all the student's course details etc) is on is different from where we host the student service. Access for information and updates for changes are made by web requests over SSL. This is easy, I'm using the libwww-perl libraries version 5.65.

My problem is that I can very easily get the student's student number from LDAP or I can very easily access Forte, but for some reason I can't do both. Simplified code is as follows:

### Forte.pm package Forte; use LWP::UserAgent; $ENV{HTTPS_VERSION} = '3'; $ENV{HTTPS_CERT_FILE} = "some.crt"; $ENV{HTTPS_KEY_FILE} = "some.key"; sub callforte { ... my $ua = new LWP::UserAgent; my $req = new HTTP::Request("POST", "$url"); ... my $res = $ua->request($req); ... } #------------------------------------------ ### LDAP.pm package LDAP; use Net::LDAPS; sub LDAP_connect { .... my $conn = new Net::LDAPS($LDAPhost, port=>$LDAPport, clientcert=>$cert, clientkey=>$key); .... } sub get_student_number { LDAP_connect(); ... my $entry = $conn->search(filter=>"(uid=$username)", base=>$base, attrs=>[qw/studentnumber/]); if($entry->code()) { html_error("Search for student ID for [$username] fail +ed:". $entry->code() . "\n"); return 0; } ... return $entry->{attrs}{studentnumber}[0]; } #------------------------------------------ #### application use Forte; use LDAP; # test for connection to db: $return = Forte::callforte($url, $input); ... if($using_portal) { # get student number $student_number = LDAP::get_student_number($username); }
If I comment out use LDAP; and set $using_portal to false, everything works as it should, the $ua->request($req) takes some time and returns with code 200 (and any data I want). If I leave $using_portal false, but uncomment use LDAP; then when I attempt to connect to the Forte database the $ua->request($req) line returns immediately with error code 500.

If I leave use LDAP; in my application but instead comment out use Net::LDAPS; in my LDAP package it works fine again, although obviously I won't be able to connect to LDAP.

Where do I even start with trying to work out what is making these two modules not play well together?

The dependencies (that I know about) and the versions I have installed are as follows:

I suspect that the problem is between the two SSLeay libraries, although I'm not sure how or why. Short of rewriting perl-ldap to replace Net::SSLeay with LWP, I'm not sure how I'd tell. Both have been compiled to use openssl version 0.9.6d. Has anyone had any experience here before?

All suggestions are appreciated. And thanks for reading this way-too-long question. :)

jarich

Replies are listed 'Best First'.
Re: Mixing modules, LWP::UserAgent and Net::LDAPS not playing well together.
by PodMaster (Abbot) on Jul 30, 2002 at 08:55 UTC
    Do your modules export any variables/functions into the caller namespace?

    That could be a problem ( without more diagnostic information its impossible to tell. For example, when the error code is 500, what is the message you get?) I seriously doubt it's a direct issue between the libwww and ldap "libraries" (they're both fairly mature, and well tested)

    ____________________________________________________
    ** The Third rule of perl club is a statement of fact: pod is sexy.

      No, my modules/packages aren't exporting anything, hence the LDAP::get_student_number($username); call.

      Diagnostic information... I'm running short on ideas of what to look for too. I've simplified the code of the problem to this:

      #!/servers/web/bin/perl -w use strict; use IO::Socket::SSL; use LWP::UserAgent; use Data::Dumper; ### LWP Stuff $ENV{HTTPS_VERSION} = '3'; # CLIENT CERT SUPPORT ### These ENV variables are required by LWP, there ### exists no other method of telling LWP which ### certificates should be used. my $certdbpath = "/some/cert/path/conf/"; $ENV{HTTPS_CERT_FILE} = "$certdbpath/client.crt"; $ENV{HTTPS_KEY_FILE} = "$certdbpath/client.key"; callforte(); exit; ############################################## sub callforte { print "This is libwww-perl-$LWP::VERSION\n"; print STDERR "This is libwww-perl-$LWP::VERSION\n"; # Check we have permission to read the certificates. open CERT, $ENV{HTTPS_CERT_FILE} or die "not cert :("; open KEY, $ENV{HTTPS_KEY_FILE} or die "not key :("; my $ua = new LWP::UserAgent; my $req = new HTTP::Request('POST', 'https://machine.edu.au:12 +3443/cgi-bin/fortecgi?'. 'pagename=CheckForte'); # This is an HTML form $req->content_type('application/x-www-form-urlencoded'); $req->content("dummy_input=1"); my $res = $ua->request($req); print STDERR $res->code."\n"; print STDERR $res->content(), "\n"; print Dumper($res); return ""; }
      You'll note the ommision of Net::LDAPS. One of the packages that Net::LDAPS needs is IO::Socket::SSL, and it seems to be the cause of my problems. :(

      My code compiles fine, and my process can read the key and certificate. When I comment out the use IO::Socket::SSL; line I connect to the Forte server without a problem, and the access log for the server records this. When I use IO::Socket::SSL, the HTTP::Response _msg is "Can't connect to machine.edu.au:123443 ()". However no message is recorded in the server error logs and no access attempt is recorded in the server access logs. Swapping the loading order isn't helping either.

      Doing a watch of netstat -tn on the forte machine records tcp connections when the script is run without IO::Socket::SSL but none when that code is included. Since these connections stick around for a few minutes I suspect that it isn't just because the script connects and then disconnects super fast.

      Localising %ENV and defining my certificates within the callforte subroutine doesn't help either, which is a shame because that would have been an easy fix.

      A separate test, changing the host I'm trying to connect to, yeilds similar results. Attempting to connect to https://www.unimelb.edu.au fails in exactly the same manner, but connecting to http://www.unimelb.edu.au responds with a "405: Method Not Allowed" which is good, because the page doesn't accept POSTs.

      Does anyone know enough about IO::Socket::SSL and LWP (in particular with Crypt::SSLeay) to help? It's definately an interaction problem because they both work perfectly on their own.

      Thanks.

      jarich

      Update: I think podmaster is right, and that it's a clash of SSL contexts. :( I don't really have an option regarding setting the ENV variables, as that's what LWP requires to work (clarified above). It really bothers me that merely useing IO::Socket::SSL tromps all over LWP::UserAgent. It bothers me that LWP::UserAgent fails quietly too. Surely something should have complained rather than it look like it's working until I attempt to use LWP to make a SSL connection to something! I guess there's a new project for me. ;)

      Update II: I think I've come up with a way to skip LDAP altogether, so at least I need not keep stressing about this. :) Yay, workaround!

        It just dawned on me that all that $ENV{HTTPS_CERT_FILE} stuff is probably the root of your problems.

        Check you Net::LDAPS documentation on passing this information through the constructor.

        Also take a look at LWP::Protocol::https10.

        # # $Id: https10.pm,v 1.1 2001/10/26 17:27:19 gisle Exp $ use strict; package LWP::Protocol::https10; # Figure out which SSL implementation to use use vars qw($SSL_CLASS); if ($IO::Socket::SSL::VERSION) { $SSL_CLASS = "IO::Socket::SSL"; # it was already loaded } else { eval { require Net::SSL; }; # from Crypt-SSLeay if ($@) { require IO::Socket::SSL; $SSL_CLASS = "IO::Socket::SSL"; } else { $SSL_CLASS = "Net::SSL"; } }
        Examine $LWP::Protocol::https10::SSL_CLASS after creating a HTTPS request, before you request it or something, and make sure it points to Net::SSL

        I don't know how significant, but the IO::Socket::SLL pod says

        Currently, the IO::Socket::INET interface as implemented by this package is not quite complete. There can be only one SSL context at a given time.

        ____________________________________________________
        ** The Third rule of perl club is a statement of fact: pod is sexy.