Optionally use the server side pager control for search results.
--- a/shelldap Sat Mar 05 00:00:00 2016 -0800
+++ b/shelldap Wed Jun 07 14:38:08 2017 -0700
@@ -138,6 +138,19 @@
=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>
Force password prompting. Useful to temporarily override cached
@@ -448,15 +461,17 @@
LDAP_OTHER
LDAP_TIMEOUT
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::LDIF;
+use Net::LDAP::Extension::SetPassword;
+use Net::LDAP::Control::Paged;
use Data::Dumper;
use File::Temp;
use Algorithm::Diff;
use Carp 'confess';
use base 'Term::Shell';
-require Net::LDAP::Extension::SetPassword;
my $conf = $main::conf;
@@ -508,6 +523,16 @@
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?)
my $s = $self->search();
die "LDAP baseDN error: ", $s->{'message'}, "\n" if $s->{'code'};
@@ -873,7 +898,8 @@
}
-### Perform an LDAP search.
+### Perform an LDAP search, optionally with the server side pager
+### control.
###
### Returns a hashref containing the return code and
### an arrayref of Net::LDAP::Entry objects.
@@ -882,11 +908,18 @@
{
my $self = shift;
my $opts = shift || {};
+ my $controls = [];
$opts->{'base'} ||= $self->base(),
$opts->{'filter'} ||= '(objectClass=*)';
$opts->{'scope'} ||= 'base';
+ my $pager;
+ if ( $conf->{'paginate'} ) {
+ $pager = Net::LDAP::Control::Paged->new( size => $conf->{'paginate'} );
+ push( @$controls, $pager );
+ }
+
my $search = sub {
return $self->ldap->search(
base => $opts->{'base'},
@@ -894,19 +927,41 @@
scope => $opts->{'scope'},
timelimit => $conf->{'timeout'},
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 = {
code => $s->code(),
- message => $s->error(),
- entries => []
+ message => $s->error()
};
- $rv->{'entries'} =
- $opts->{'scope'} eq 'base' ? [ $s->shift_entry() ] : [ $s->entries() ];
+ if ( $opts->{'scope'} eq 'base' ) {
+ $rv->{'entries'} = [ $s->shift_entry() ]
+ }
+ else {
+ $rv->{'entries'} = $entries;
+ }
return $rv;
}
@@ -2322,6 +2377,7 @@
'binddn|D=s',
'basedn|b=s',
'cacheage=i',
+ 'paginate=i',
'promptpass|W',
'timeout=i',
'sasl|Y=s',
@@ -2350,7 +2406,6 @@
while ( my ($k, $v) = each %{$conf} ) { $conf->{ $k } = $v }
}
-
# defaults
$conf->{'configfile'} ||= "$ENV{'HOME'}/.shelldap.rc";
$conf->{'cacheage'} ||= 300;