Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add perl-script hack to work around PHP LDAP paging limitation

  • Loading branch information...
commit adde0b21f2bc14dee7f7775911ecda89250ad0e6 1 parent f89a83b
@brki authored
Showing with 257 additions and 0 deletions.
  1. +134 −0 auth/ldap/auth.php
  2. +123 −0 auth/ldap/cli/perl_get_users.pl
View
134 auth/ldap/auth.php
@@ -648,6 +648,18 @@ function sync_users($do_updates=true) {
if (empty($context)) {
continue;
}
+
+ // perl hack to get around missing php paging limit:
+ if (!$this->perl_get_users($context, $filter, 900)) {
+ // if something went wrong, clean up and exit
+ $dbman->drop_temp_table($table);
+ $this->ldap_close();
+ print "Exiting sync script; something went wrong while trying to get a list of users\n\n";
+ exit;
+ }
+ continue;
+ // not reached, because the perl hack function is taking care of this.
+
if ($this->config->search_sub) {
//use ldap_search to find first user from subtree
$ldap_result = ldap_search($ldapconnection, $context,
@@ -2022,4 +2034,126 @@ function ldap_find_userdn($ldapconnection, $extusername) {
$this->config->user_attribute, $this->config->search_sub);
}
+ /**
+ * Use perl to get a list of users from LDAP, and insert them into the temp table created for processing ldap users.
+ *
+ * This function is necessary because PHP does not support LDAP paging, and so
+ * a query that would return > 1000 records would return only 1000 records.
+ * If possible, raise the limit on the LDAP side. If the LDAP administrator refuses, use this workaround.
+ * See https://bugs.php.net/bug.php?id=42060 .
+ *
+ * @param string $context ldap context
+ * @param string $filter ldap filter
+ * @param int $pagesize how many records to fetch at a time
+ * @return boolean true on success. If false, something bad went wrong and execution should be aborted after cleanup.
+ */
+ function perl_get_users($context, $filter, $pagesize)
+ {
+ global $CFG;
+ $textlib = textlib_get_instance();
+
+ // Remove any files that were abandoned due to a failed previous run (although every effort
+ // is made to not leave old files around), and then create the directory used for passing
+ // params to the perl script.
+ $tempdir = $this->perl_temp_dir();
+ remove_dir($tempdir);
+ if (!check_dir_exists($tempdir)) {
+ echo "Unable to create temporary directory $tempdir for perl script.\n";
+ return false;
+ }
+
+ // Fetch a list of usernames from ldap, using perl because as of PHP 5.3, PHP does not support LDAP paging.
+ if (!$paramfile = tempnam($tempdir, 'perlldap')) {
+ echo "Unable to create temporary file $paramfile for perl script.\n";
+ return false;
+ }
+ if (!chmod($paramfile, 0600)) {
+ echo "Unable to change permissions on temporary file $paramfile.\n";
+ return false;
+ }
+ $params = 'ldap_server :: ' . implode("','", explode(";",$this->config->host_url)) . "\n"
+ . 'ad_ldap_dn :: ' . $this->config->bind_dn . "\n"
+ . 'ad_ldap_password :: ' . $this->config->bind_pw . "\n"
+ . 'ad_ldap_version :: ' . $this->config->ldap_version . "\n"
+ . 'size_limit :: ' . $pagesize . "\n"
+ . 'base :: ' . $context . "\n"
+ . 'scope :: ' . ($this->config->search_sub ? "sub" : "base") . "\n"
+ . 'filter :: ' . $filter . "\n"
+ . 'attrs :: ' . $this->config->user_attribute . "\n";
+ file_put_contents($paramfile, $params);
+
+ echo "Getting users from ldap for context: '$context' ...\n";
+ $perl_script = $CFG->dirroot. '/auth/' . $this->authtype . '/cli/perl_get_users.pl';
+ exec('/usr/bin/perl ' . $perl_script . ' ' . escapeshellarg($paramfile), $ldap_result, $ldap_status);
+ unlink($paramfile);
+ echo "Finished getting users from ldap for context '$context'.\n";
+
+ switch ($ldap_status) {
+ case 0:
+ break;
+
+ case 2:
+ print get_string('auth_ldap_connecterror', 'auth');
+ return false;
+ break;
+
+ case 3:
+ print get_string('auth_ldap_binderror', 'auth');
+ return false;
+ break;
+
+ case 4:
+ print "LDAP param file ($paramfile) could not be read";
+ return false;
+ break;
+
+ case 5:
+ print "LDAP param file ($paramfile) did not contain expected variables";
+ return false;
+ break;
+
+ default:
+ print get_string('auth_ldap_miscerror', 'auth');
+ return false;
+ break;
+ }
+
+ if(!$ldap_result) {
+ // no users matched, but this is not an error
+ return true;
+ }
+
+ // Insert the usernames into the temp table, so that they can later be synchronized to the user table.
+ foreach($ldap_result as $ldap_output) {
+ $output = explode(' :: ', $ldap_output);
+
+ // Avoid creating users from warnings or debug output from the perl script: all usernames are
+ // expected to be prefixed with 'ldapattr :: '.
+ if ($output[0] !== 'ldapattr' ) {
+ print "WARNING: unexpected output from perl script: $ldap_output\n";
+ continue;
+ }
+ $username = $textlib->convert(trim($output[1]), $this->config->ldapencoding, 'utf-8');
+
+ if ($username) {
+ $this->ldap_bulk_insert($username);
+ }
+ }
+ unset ($ldap_result);
+ return true;
+ }
+
+ function perl_temp_dir()
+ {
+ global $CFG;
+
+ if (isset($CFG->ldap_perl_temp_dir)) {
+ $base = $CFG->ldap_perl_temp_dir;
+ } else {
+ $base = $CFG->dataroot;
+ }
+ // Append something to the base, to avoid deleting anything important in case of misconfiguration.
+ return $base . '/temp/perlldap';
+ }
+
} // End of the class
View
123 auth/ldap/cli/perl_get_users.pl
@@ -0,0 +1,123 @@
+#
+# This script will read usernames from the LDAP server
+# and output them in the form:
+# ldapattr :: username-1
+# ldapattr :: username-2
+# ...
+# ldapattr :: username-n
+
+#
+# This script is based on the one posted by Olumuyiwa Taiwo to http://moodle.org/mod/forum/discuss.php?d=49336
+# It was updated by Brian King (brian of liip ch) in December 2011
+# * use strict mode for the perl script
+# * adjusted to work with Moodle 2.1 and 2.2.
+# * adjusted to pass parameters to the perl script via a file instead of in the exec call (moderately more secure)
+
+use strict;
+use Net::LDAP;
+use Net::LDAP::Control::Paged;
+use Net::LDAP::Constant qw( LDAP_CONTROL_PAGED );
+
+my $paramFile = $ARGV[0];
+
+if (not -f $paramFile) {
+ exit 4; # Let PHP deal with the exit status
+}
+
+# find out where and what to get from ldap
+my %var = ();
+open FILE,'<', $paramFile or die 'died: '.$!;
+my @lines = <FILE>;
+chomp(@lines);
+foreach my $line (@lines) {
+ my ($key, $value) = split(' :: ',$line, 2);
+ $var{$key} = $value;
+}
+close FILE;
+
+my @ldap_server;
+
+my @variables = (
+ 'ldap_server',
+ 'ad_ldap_dn',
+ 'ad_ldap_password',
+ 'ad_ldap_version',
+ 'size_limit',
+ 'base',
+ 'scope',
+ 'filter',
+ 'attrs',
+);
+
+foreach my $variable (@variables) {
+ if ( not defined $var{$variable} ) {
+ exit 5; # Let PHP deal with the exit status
+ }
+}
+
+my $ad_ldap = Net::LDAP->new( $var{'ldap_server'}, version => $var{'ad_ldap_version'} ) or exit 2; # Let PHP deal with the exit status
+
+my $bind = $ad_ldap->bind($var{'ad_ldap_dn'}, password => $var{'ad_ldap_password'}) or exit 3; # Let PHP deal with the exit status
+
+my $page = Net::LDAP::Control::Paged->new( size => $var{'size_limit'} );
+
+my $cookie;
+
+my @args = (
+'base' => $var{'base'},
+'scope' => $var{'scope'},
+'filter' => $var{'filter'},
+'attrs' => [ $var{'attrs'} ],
+'control' => [ $page ],
+);
+
+my $count = 0;
+
+while(1) {
+ # Perform search
+ my $result = $ad_ldap->search( @args );
+
+ my $href = $result->as_struct;
+
+ # get an array of the DN names
+ my @arrayOfDNs = keys %$href; # use DN hashes
+
+ # process each DN using it as a key
+ foreach ( @arrayOfDNs ) {
+ my $valref = $$href{$_};
+ # get an array of the attribute names
+ # passed for this one DN.
+ my @arrayOfAttrs = sort keys %$valref; #use Attr hashes
+ my $attrName;
+ foreach $attrName (@arrayOfAttrs) {
+
+ # skip any binary data: yuck!
+ next if ( $attrName =~ /;binary$/ );
+
+ # get the attribute value (pointer) using the
+ # attribute name as the hash
+ my $attrVal = @$valref{$attrName};
+ print "ldapattr :: @$attrVal\n";
+ }
+ # End of that DN
+ $count++;
+ }
+
+ # Only continue on LDAP_SUCCESS
+ $result->code and last;
+
+ # Get cookie from paged control
+ my($resp) = $result->control( LDAP_CONTROL_PAGED ) or last;
+ $cookie = $resp->cookie or last;
+
+ # Set cookie in paged control
+ $page->cookie($cookie);
+}
+
+if ($cookie) {
+ # We had an abnormal exit, so let the server know we do not want any more
+ $page->cookie($cookie);
+ $page->size(0);
+ my $result = $ad_ldap->search( @args );
+}
+$ad_ldap->unbind;
Please sign in to comment.
Something went wrong with that request. Please try again.