Optionally use the server side pager control for search results.

FossilOrigin-Name: 80a69ef0eaf294692b133e14c1611e6c60373da11c2edeae1d2554dbdb7b1bf3
This commit is contained in:
Mahlon E. Smith 2017-06-07 21:38:07 +00:00
parent 08ee88750d
commit 2a068606e6

View file

@ -138,6 +138,19 @@ try and ask the server for a sane default.
=over 4 =over 4
=item B<paginate>
Integer. If enabled, shelldap will attempt to use server side
pagination to build listings. Note: if you're using this to avoid
sizelimit errors, you'll likely need server configuration to raise the
limits for paginated results.
--paginate 100
=back
=over 4
=item B<promptpass> =item B<promptpass>
Force password prompting. Useful to temporarily override cached Force password prompting. Useful to temporarily override cached
@ -448,15 +461,17 @@ use Net::LDAP qw/
LDAP_OTHER LDAP_OTHER
LDAP_TIMEOUT LDAP_TIMEOUT
LDAP_NO_MEMORY LDAP_NO_MEMORY
LDAP_CONNECT_ERROR /; LDAP_CONNECT_ERROR
LDAP_CONTROL_PAGED /;
use Net::LDAP::Util qw/ canonical_dn ldap_explode_dn /; use Net::LDAP::Util qw/ canonical_dn ldap_explode_dn /;
use Net::LDAP::LDIF; use Net::LDAP::LDIF;
use Net::LDAP::Extension::SetPassword;
use Net::LDAP::Control::Paged;
use Data::Dumper; use Data::Dumper;
use File::Temp; use File::Temp;
use Algorithm::Diff; use Algorithm::Diff;
use Carp 'confess'; use Carp 'confess';
use base 'Term::Shell'; use base 'Term::Shell';
require Net::LDAP::Extension::SetPassword;
my $conf = $main::conf; my $conf = $main::conf;
@ -508,6 +523,16 @@ sub init
print "Cipher in use: ", $self->ldap()->cipher(), "\n"; print "Cipher in use: ", $self->ldap()->cipher(), "\n";
} }
# check for the pagination extension on the server early, and bail
# if necessary.
if ( $conf->{'paginate'} && $conf->{'paginate'} =~ /^\d+$/ && $conf->{'paginate'} > 0 ) {
my $has_pagination = ( grep $_ eq LDAP_CONTROL_PAGED, $self->{'root_dse'}->get_value('supportedControl') );
die "Server pagination is enabled, but the server doesn't seem to support it.\n" unless $has_pagination;
}
else {
$conf->{'paginate'} = undef;
}
# try an initial search and bail early if it doesn't work. (bad baseDN?) # try an initial search and bail early if it doesn't work. (bad baseDN?)
my $s = $self->search(); my $s = $self->search();
die "LDAP baseDN error: ", $s->{'message'}, "\n" if $s->{'code'}; die "LDAP baseDN error: ", $s->{'message'}, "\n" if $s->{'code'};
@ -873,7 +898,8 @@ sub display
} }
### Perform an LDAP search. ### Perform an LDAP search, optionally with the server side pager
### control.
### ###
### Returns a hashref containing the return code and ### Returns a hashref containing the return code and
### an arrayref of Net::LDAP::Entry objects. ### an arrayref of Net::LDAP::Entry objects.
@ -882,11 +908,18 @@ sub search
{ {
my $self = shift; my $self = shift;
my $opts = shift || {}; my $opts = shift || {};
my $controls = [];
$opts->{'base'} ||= $self->base(), $opts->{'base'} ||= $self->base(),
$opts->{'filter'} ||= '(objectClass=*)'; $opts->{'filter'} ||= '(objectClass=*)';
$opts->{'scope'} ||= 'base'; $opts->{'scope'} ||= 'base';
my $pager;
if ( $conf->{'paginate'} ) {
$pager = Net::LDAP::Control::Paged->new( size => $conf->{'paginate'} );
push( @$controls, $pager );
}
my $search = sub { my $search = sub {
return $self->ldap->search( return $self->ldap->search(
base => $opts->{'base'}, base => $opts->{'base'},
@ -894,19 +927,41 @@ sub search
scope => $opts->{'scope'}, scope => $opts->{'scope'},
timelimit => $conf->{'timeout'}, timelimit => $conf->{'timeout'},
typesonly => ! $opts->{'vals'}, typesonly => ! $opts->{'vals'},
attrs => $opts->{'attrs'} || ['*'] attrs => $opts->{'attrs'} || ['*'],
control => $controls
); );
}; };
my $s = $self->with_retry( $search ); my $s;
my $entries = [];
my $token = '-';
if ( $conf->{'paginate'} ) {
while( $token ) {
$s = $self->with_retry( $search );
push( @$entries, $s->entries() );
my $page_response = $s->control( LDAP_CONTROL_PAGED ) or last;
$token = $page_response->cookie;
$pager->cookie( $token );
}
}
else {
$s = $self->with_retry( $search );
$entries = [ $s->entries() ];
}
my $rv = { my $rv = {
code => $s->code(), code => $s->code(),
message => $s->error(), message => $s->error()
entries => []
}; };
$rv->{'entries'} = if ( $opts->{'scope'} eq 'base' ) {
$opts->{'scope'} eq 'base' ? [ $s->shift_entry() ] : [ $s->entries() ]; $rv->{'entries'} = [ $s->shift_entry() ]
}
else {
$rv->{'entries'} = $entries;
}
return $rv; return $rv;
} }
@ -2322,6 +2377,7 @@ Getopt::Long::GetOptions(
'binddn|D=s', 'binddn|D=s',
'basedn|b=s', 'basedn|b=s',
'cacheage=i', 'cacheage=i',
'paginate=i',
'promptpass|W', 'promptpass|W',
'timeout=i', 'timeout=i',
'sasl|Y=s', 'sasl|Y=s',
@ -2350,7 +2406,6 @@ if ( $conf->{'configfile'} ) {
while ( my ($k, $v) = each %{$conf} ) { $conf->{ $k } = $v } while ( my ($k, $v) = each %{$conf} ) { $conf->{ $k } = $v }
} }
# defaults # defaults
$conf->{'configfile'} ||= "$ENV{'HOME'}/.shelldap.rc"; $conf->{'configfile'} ||= "$ENV{'HOME'}/.shelldap.rc";
$conf->{'cacheage'} ||= 300; $conf->{'cacheage'} ||= 300;