diff -r 80ec00959fbd -r a1aa55019077 shelldap --- 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 + +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 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;