# Copyright (c) 2001 Dominic Mitchell. # Portions Copyright (c) 2007 Gavin Henry - , # Suretec Systems Ltd. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # # @(#) $Id: LDAP.pm 1318 2007-03-29 12:05:03Z dom $ # package Template::Plugin::LDAP; use strict; use vars qw( $VERSION ); use base qw( Template::Plugin ); use Net::LDAP; $VERSION = ( qw( $Revision: 1318 $ ) )[1]; sub new { my $class = shift; my $context = shift; my $self = {}; bless $self, $class; $self->_context( $context ); $self->connect( @_ ) if @_; return $self; } sub _context { my $self = shift; $self->{ _context } = $_[0] if @_; return $self->{ _context }; } sub _ldap { my $self = shift; $self->{ _ldap } = $_[0] if @_; return $self->{ _ldap }; } # connect(host[:port], user, password); sub connect { my $self = shift; my $params = ref $_[-1] eq 'HASH' ? pop( @_ ) : {}; my ( $host, $port, $user, $pass ); $host = shift || $params->{ host } || return $self->_throw( "no ldap host specified" ); $port = ( $host =~ m/:(\d+)$/ )[0] || $params->{ port } || getservbyname( "ldap", "tcp" ) || 389; $user = shift || $params->{ user }; $pass = shift || $params->{ pass }; my $ldap = Net::LDAP->new( $host, port => $port ) or return $self->_throw( "ldap connect: $@" ); if ( $user || $pass ) { $ldap->bind( $user, password => $pass ); } else { $ldap->bind; # Anonymous bind. } $self->_ldap( $ldap ); return ''; } # search takes the same arguments as Net::LDAP->search(). sub search { my $self = shift; my $params = ref $_[-1] eq 'HASH' ? pop( @_ ) : { @_ }; my $mesg = $self->_ldap->search( %$params ); return $self->_throw( $mesg->error ) if $mesg->code; return Template::Plugin::LDAP::Iterator->new( $mesg ); } package Template::Plugin::LDAP::Iterator; use strict; use base qw( Template::Iterator ); sub new { my ( $class, $mesg, $params ) = @_; my $self = bless {}, $class; $self->_mesg( $mesg ); return $self; } { my @accessors = qw( _mesg _started PREV NEXT ITEM FIRST LAST COUNT INDEX ); foreach my $a ( @accessors ) { no strict 'refs'; *{ $a } = sub { my $self = shift; $self->{ $a } = $_[0] if @_; return $self->{ $a }; } } } sub get_first { my $self = shift; $self->_started( 1 ); $self->PREV( undef ); $self->ITEM( undef ); $self->FIRST( 2 ); # ??? $self->LAST( 0 ); $self->COUNT( 0 ); $self->INDEX( -1 ); $self->_fetchentry; return $self->get_next; } sub get_next { my $self = shift; my $data; $self->INDEX( $self->INDEX + 1 ); $self->COUNT( $self->COUNT + 1 ); $self->FIRST( $self->FIRST - 1 ) if $self->FIRST; return ( undef, Template::Constants::STATUS_DONE ) unless $data = $self->NEXT; $self->PREV( $self->ITEM ); $self->_fetchentry; $self->ITEM( $data ); return ( $data, Template::Constants::STATUS_OK ); } sub get { my $self = shift; my ( $data, $error ) = $self->STARTED ? $self->get_next : $self->get_first; return $data; } sub get_all { my $self = shift; my $mesg = $self->_mesg; my $error; my $data = [ map { Template::Plugin::LDAP::Entry->new( $_ ) } $mesg->entries ]; unshift @$data, $self->NEXT # XXX Is this needed? if $self->NEXT; $self->LAST( 1 ); $self->NEXT( undef ); return $data; } sub _fetchentry { my $self = shift; my $mesg = $self->_mesg; # XXX We should probably use our own wrapper object here. my $data = $mesg->shift_entry || do { $self->LAST( 1 ); $self->NEXT( undef ); return; }; $data = Template::Plugin::LDAP::Entry->new( $data ); $self->NEXT( $data ); return; } package Template::Plugin::LDAP::Entry; use vars qw( $AUTOLOAD ); sub new { my ( $class, $entry ) = @_; my $self = { _entry => $entry }; bless $self, $class; return $self; } sub dn { my $self = shift; return $self->{ _entry }->dn; } sub AUTOLOAD { my $self = shift; ( my $var = $AUTOLOAD ) =~ s/.*:://; my $val; if ( $self->{ _entry }->exists( $var ) ) { return $self->{ _entry }->get_value( $var ); } else { return ""; } } 1; __END__ =head1 NAME Template::Plugin::LDAP - Handle LDAP queries in TT pages. =head1 SYNOPSIS # Bind anonymously. [% USE LDAP('ldap.lan') %] # Authenticate. [% USE LDAP('ldap.lan', 'user', 'password') %] # Connect explicitly [% USE LDAP %] [% LDAP.connect('ldap.lan') %] [% FOREACH entry = LDAP.search( base = 'dc=myco,dc=com', filter = '(objectClass=person)', attrs = [ 'email', 'cn' ] ) %] Distinguished Name Is [% entry.dn %] Email: [% entry.cn %] <[% entry.email %]> [% END %] =head1 DESCRIPTION This is a plugin for the Template Toolkit to do LDAP queries. It does not do updates. Mostly, it is similiar in design to the DBI plugin, except where I copied it wrong. :-) Basically, pass in a hostname and optionally a username/password to the constructor. To do a search, you need to specify at least base and filter arguments to the search method, but have a look at Net::LDAP(3) (the search method) because that is what is being used underneath and there are quite a few options. The entries that you get back from the search are at present very simplistic and really only meant for display purposes only. If I need to do updates later, that functionality might be added. =head1 METHODS =head2 new =head2 connect =head2 search =head2 get_first =head2 get_next =head2 get =head2 get_all =head2 dn =head1 AUTHOR Dominic Mitchell Edom@happygiraffe.netE =head1 MAINTAINER Suretec Systems Ltd., Gavin Henry Eghenry@suretecsystems.com =head1 SEE ALSO Net::LDAP(3), Template::Plugin(3), Template::Pluigin::DBI(3). =cut # Local Variables: # mode: cperl # cperl-indent-level: 4 # indent-tabs-mode: nil # End: # vim: ai et sw=4 :