From a84d86b41a0e924874ba361433f6bf1df464b060 Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Tue, 6 May 2014 09:34:56 +0200 Subject: [PATCH 01/32] Initial release: Protocol / stream implementation --- .../php/peer/ldap/util/BerStream.class.php | 189 ++++++++++++++++++ .../php/peer/ldap/util/LdapProtocol.class.php | 53 +++++ 2 files changed, 242 insertions(+) create mode 100755 src/main/php/peer/ldap/util/BerStream.class.php create mode 100755 src/main/php/peer/ldap/util/LdapProtocol.class.php diff --git a/src/main/php/peer/ldap/util/BerStream.class.php b/src/main/php/peer/ldap/util/BerStream.class.php new file mode 100755 index 00000000..b4a82f88 --- /dev/null +++ b/src/main/php/peer/ldap/util/BerStream.class.php @@ -0,0 +1,189 @@ +<?php namespace peer\ldap\util; + +class BerStream extends \lang\Object { + const EOC = 0; + const BOOLEAN = 1; + const INTEGER = 2; + const BITSTRING = 3; + const OCTETSTRING = 4; + const NULL = 5; + const OID = 6; + const OBJECTDESCRIPTOR = 7; + const EXTERNAL = 8; + const REAL = 9; + const ENUMERATION = 10; + const PDV = 11; + const UTF8STRING = 12; + const RELATIVEOID = 13; + const SEQUENCE = 16; + const SET = 17; + const NUMERICSTRING = 18; + const PRINTABLESTRING = 19; + const T61STRING = 20; + const VIDEOTEXSTRING = 21; + const IA5STRING = 22; + const UTCTIME = 23; + const GENERALIZEDTIME = 24; + const GRAPHICSTRING = 25; + const VISIBLESTRING = 26; + const GENERALSTRING = 28; + const UNIVERSALSTRING = 29; + const CHARACTERSTRING = 30; + const BMPSTRING = 31; + const CONSTRUCTOR = 32; + const CONTEXT = 128; + + const SEQ_CTOR = 48; // (SEQUENCE | CONSTRUCTOR) + + protected $seq= ['']; + + public function __construct(\io\streams\InputStream $in, \io\streams\OutputStream $out) { + $this->in= $in; + $this->out= $out; + } + + public function startSequence($tag= self::SEQ_CTOR) { + array_unshift($this->seq, ''); + $this->writeByte($tag); + } + + protected function encodeLength($l) { + if ($l <= 0x7f) { + return pack('C', $l); + } else if ($l <= 0xff) { + return "\x81".pack('C', $l); + } else if ($l <= 0xffff) { + return "\x82".pack('CC', $l >> 8, $l); + } else if ($l <= 0xffffff) { + return "\x83".pack('CCC', $l << 16, $l >> 8, $l); + } else { + throw new \lang\IllegalStateException('Length too long: '.$l); + } + } + + public function write($raw) { + $this->seq[0].= $raw; + } + + public function writeByte($b) { + $this->seq[0].= pack('C', $b); + } + + public function writeLength($l) { + $this->seq[0].= $this->encodeLength($l); + } + + public function writeInt($i, $tag= self::INTEGER) { + if ($i < -0xffffff || $i >= 0xffffff) { + $len= 4; + } else if ($i < -0xffff || $i >= 0xffff) { + $len= 3; + } else if ($i < -0xff || $i >= 0xff) { + $len= 2; + } else { + $len= 1; + } + $this->seq[0].= pack('CC', $tag, $len).substr(pack('N', $i), -$len); + } + + public function writeNull() { + $this->seq[0].= pack('C', self::NULL)."\x00"; + } + + public function writeBoolean($b, $tag= self::BOOLEAN) { + $this->seq[0].= pack('C', $tag)."\x01".($b ? "\xff" : "\x00"); + } + + public function writeString($s, $tag= self::OCTETSTRING) { + $length= $this->encodeLength(strlen($s)); + $this->seq[0].= pack('C', $tag).$length.$s; + } + + public function writeEnumeration($e, $tag= self::ENUMERATION) { + $this->writeInt($e, $tag); + } + + public function endSequence() { + $length= $this->encodeLength(strlen($this->seq[0]) - 1); + $seq= array_shift($this->seq); + $this->seq[0].= $seq{0}.$length.substr($seq, 1); + } + + private function chars($bytes, $start, $offset) { + $s= ''; + for ($j= $start; $j < min($offset, strlen($bytes)); $j++) { + $c= $bytes{$j}; + $s.= ($c < "\x20" || $c > "\x7F" ? '.' : $c); + } + return $s; + } + + private function dump($bytes, $message= null) { + $n= strlen($bytes); + $next= ' '; + if (null === $message) { + $s= ''; + $p= 74; + } else { + $s= $message.' '; + $p= 74 - strlen($message) - 1; + } + $s.= str_pad('== ('.$n." bytes) ==\n", $p, '=', STR_PAD_LEFT); + $o= 0; + while ($o < $n) { + $s.= sprintf('%04x: ', $o); + for ($i= 0; $i < 16; $i++) { + if ($i + $o >= $n) { + $s.= 7 === $i ? ' ' : ' '; + } else { + $s.= sprintf('%02x %s', ord($bytes{$i + $o}), 7 === $i ? ' ' : ''); + } + } + $o+= $i; + $s.= '|'.str_pad($this->chars($bytes, $o - 16, $o), 16, ' ', STR_PAD_RIGHT)."|\n"; + } + return $s; + } + + public function flush() { + \util\cmd\Console::writeLine($this->dump($this->seq[0], '>>>')); + $this->out->write($this->seq[0]); + } + + public function readSequence($tag= self::SEQ_CTOR) { + $head= unpack('Ctag/Cl0/a3rest', $this->in->read(5)); + if ($head['tag'] !== $tag) { + throw new \lang\IllegalStateException(sprintf('Expected %0x, have %0x', $tag, $head['tag'])); + } + + if ($head['l0'] <= 0x7f) { + $length= $head['l0']; + $s= $head['rest']; + } else if (0x81 === $head['l0']) { + $l= unpack('C', $head['rest']); + $length= $l[1]; + $s= substr($head['rest'], -2); + } else if (0x82 === $head['l0']) { + $l= unpack('C2', $head['rest']); + $length= $l[1] * 0x100 + $l[2]; + $s= substr($head['rest'], -1); + } else if (0x83 === $head['l0']) { + $l= unpack('C3', $head['rest']); + $length= $l[1] * 0x10000 + $l[2] * 0x100 + $l[3]; + $s= ''; + } else { + throw new \lang\IllegalStateException('Length too long: '.$head['l0']); + } + + while (strlen($s) < $length) { + $s.= $this->in->read($length - strlen($s)); + } + return $s; + } + + public function read() { + $seq= $this->readSequence(); + \util\cmd\Console::writeLine($this->dump($seq, '<<<')); + return new \lang\types\Bytes($seq); + } +} \ No newline at end of file diff --git a/src/main/php/peer/ldap/util/LdapProtocol.class.php b/src/main/php/peer/ldap/util/LdapProtocol.class.php new file mode 100755 index 00000000..af2c4821 --- /dev/null +++ b/src/main/php/peer/ldap/util/LdapProtocol.class.php @@ -0,0 +1,53 @@ +<?php namespace peer\ldap\util; + +class LdapProtocol extends \lang\Object { + const REQ_BIND = 0x60; + const REQ_UNBIND = 0x42; + const REQ_SEARCH = 0x63; + const REQ_MODIFY = 0x66; + const REQ_ADD = 0x68; + const REQ_DELETE = 0x4a; + const REQ_MODRDN = 0x6c; + const REQ_COMPARE = 0x6e; + const REQ_ABANDON = 0x50; + const REQ_EXTENSION = 0x77; + + const SCOPE_BASE_OBJECT = 0; + const SCOPE_ONE_LEVEL = 1; + const SCOPE_SUBTREE = 2; + + const NEVER_DEREF_ALIASES = 0; + const DEREF_IN_SEARCHING = 1; + const DEREF_BASE_OBJECT = 2; + const DEREF_ALWAYS = 3; + + protected $messageId= 0; + + public function __construct(\peer\Socket $sock) { + $this->stream= new BerStream( + $sock->getInputStream(), + $sock->getOutputStream() + ); + } + + protected function nextMessageId() { + if (++$this->messageId >= 0x7fffffff) { + $this->messageId= 1; + } + return $this->messageId; + } + + public function send($request) { + $this->stream->startSequence(); + $this->stream->writeInt($this->nextMessageId()); + + $this->stream->startSequence($request['op']); + call_user_func($request['write'], $this->stream); + $this->stream->endSequence(); + + $this->stream->endSequence(); + $this->stream->flush(); + + return $this->stream->read(); + } +} \ No newline at end of file From 9c3e84d91f8b7c00f8e1279b943a4f462cb97022 Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Tue, 6 May 2014 17:26:27 +0200 Subject: [PATCH 02/32] Utilize new BufferedInputStream::pushBack() See xp-framework/core#16 --- .../php/peer/ldap/util/BerStream.class.php | 229 +++++++++++++----- 1 file changed, 167 insertions(+), 62 deletions(-) diff --git a/src/main/php/peer/ldap/util/BerStream.class.php b/src/main/php/peer/ldap/util/BerStream.class.php index b4a82f88..ea38e847 100755 --- a/src/main/php/peer/ldap/util/BerStream.class.php +++ b/src/main/php/peer/ldap/util/BerStream.class.php @@ -1,5 +1,9 @@ <?php namespace peer\ldap\util; +use io\streams\BufferedInputStream; +use io\streams\InputStream; +use io\streams\OutputStream; + class BerStream extends \lang\Object { const EOC = 0; const BOOLEAN = 1; @@ -35,18 +39,98 @@ class BerStream extends \lang\Object { const SEQ_CTOR = 48; // (SEQUENCE | CONSTRUCTOR) - protected $seq= ['']; + protected $write= ['']; + + /** + * Constructor + * + * @param io.streams.InputStream $in + * @param io.streams.OutputStream $out + */ + public function __construct(InputStream $in, OutputStream $out) { - public function __construct(\io\streams\InputStream $in, \io\streams\OutputStream $out) { - $this->in= $in; + // Debug + $in= newinstance('io.streams.InputStream', [$in], [ + 'backing' => null, + '__construct' => function($backing) { $this->backing= $backing; }, + 'read' => function($length= 8192) { + $chunk= $this->backing->read($length); + if (null !== $chunk) { + \util\cmd\Console::writeLine(BerStream::dump($chunk, '<<<')); + } + return $chunk; + }, + 'available' => function() { return $this->backing->available(); }, + 'close' => function() { $this->backing->close(); } + ]); + $out= newinstance('io.streams.OutputStream', [$out], [ + 'backing' => null, + '__construct' => function($backing) { $this->backing= $backing; }, + 'write' => function($chunk) { + \util\cmd\Console::writeLine(BerStream::dump($chunk, '>>>')); + return $this->backing->write($chunk); + }, + 'flush' => function() { return $this->backing->flush(); }, + 'close' => function() { $this->backing->close(); } + ]); + + $this->in= $in instanceof BufferedInputStream ? $in : new BufferedInputStream($in); $this->out= $out; } + private static function chars($bytes, $start, $offset) { + $s= ''; + for ($j= $start; $j < min($offset, strlen($bytes)); $j++) { + $c= $bytes{$j}; + $s.= ($c < "\x20" || $c > "\x7F" ? '.' : $c); + } + return $s; + } + + public static function dump($bytes, $message= null) { + $n= strlen($bytes); + $next= ' '; + if (null === $message) { + $s= ''; + $p= 74; + } else { + $s= $message.' '; + $p= 74 - strlen($message) - 1; + } + $s.= str_pad('== ('.$n." bytes) ==\n", $p, '=', STR_PAD_LEFT); + $o= 0; + while ($o < $n) { + $s.= sprintf('%04x: ', $o); + for ($i= 0; $i < 16; $i++) { + if ($i + $o >= $n) { + $s.= 7 === $i ? ' ' : ' '; + } else { + $s.= sprintf('%02x %s', ord($bytes{$i + $o}), 7 === $i ? ' ' : ''); + } + } + $o+= $i; + $s.= '|'.str_pad(self::chars($bytes, $o - 16, $o), 16, ' ', STR_PAD_RIGHT)."|\n"; + } + return $s; + } + + /** + * Starts writing a sequence + * + * @param int $tag + */ public function startSequence($tag= self::SEQ_CTOR) { - array_unshift($this->seq, ''); + array_unshift($this->write, ''); $this->writeByte($tag); } + /** + * Encode length + * + * @param int $l + * @return string encoded bytes + * @throws lang.IllegalStateException if length is > 0xffffff + */ protected function encodeLength($l) { if ($l <= 0x7f) { return pack('C', $l); @@ -61,18 +145,39 @@ protected function encodeLength($l) { } } + /** + * Write raw bytes to current sequence + * + * @param string $raw + */ public function write($raw) { - $this->seq[0].= $raw; + $this->write[0].= $raw; } + /** + * Write single byte to current sequence + * + * @param int $b + */ public function writeByte($b) { - $this->seq[0].= pack('C', $b); + $this->write[0].= pack('C', $b); } + /** + * Write length to current sequence + * + * @param int $l + */ public function writeLength($l) { - $this->seq[0].= $this->encodeLength($l); + $this->write[0].= $this->encodeLength($l); } + /** + * Write integer to current sequence + * + * @param int $i + * @param int $tag + */ public function writeInt($i, $tag= self::INTEGER) { if ($i < -0xffffff || $i >= 0xffffff) { $len= 4; @@ -83,73 +188,73 @@ public function writeInt($i, $tag= self::INTEGER) { } else { $len= 1; } - $this->seq[0].= pack('CC', $tag, $len).substr(pack('N', $i), -$len); + $this->write[0].= pack('CC', $tag, $len).substr(pack('N', $i), -$len); } + /** + * Write NULL value to current sequence + * + * @param string $raw + */ public function writeNull() { - $this->seq[0].= pack('C', self::NULL)."\x00"; + $this->write[0].= pack('C', self::NULL)."\x00"; } + /** + * Write boolean to current sequence + * + * @param bool $b + * @param int $tag + */ public function writeBoolean($b, $tag= self::BOOLEAN) { - $this->seq[0].= pack('C', $tag)."\x01".($b ? "\xff" : "\x00"); + $this->write[0].= pack('C', $tag)."\x01".($b ? "\xff" : "\x00"); } + /** + * Write string to current sequence + * + * @param string $s + * @param int $tag + */ public function writeString($s, $tag= self::OCTETSTRING) { $length= $this->encodeLength(strlen($s)); - $this->seq[0].= pack('C', $tag).$length.$s; + $this->write[0].= pack('C', $tag).$length.$s; } + /** + * Write enumeration to current sequence + * + * @param int $e + * @param int $tag + */ public function writeEnumeration($e, $tag= self::ENUMERATION) { $this->writeInt($e, $tag); } + /** + * Ends current sequences + */ public function endSequence() { - $length= $this->encodeLength(strlen($this->seq[0]) - 1); - $seq= array_shift($this->seq); - $this->seq[0].= $seq{0}.$length.substr($seq, 1); - } - - private function chars($bytes, $start, $offset) { - $s= ''; - for ($j= $start; $j < min($offset, strlen($bytes)); $j++) { - $c= $bytes{$j}; - $s.= ($c < "\x20" || $c > "\x7F" ? '.' : $c); - } - return $s; - } - - private function dump($bytes, $message= null) { - $n= strlen($bytes); - $next= ' '; - if (null === $message) { - $s= ''; - $p= 74; - } else { - $s= $message.' '; - $p= 74 - strlen($message) - 1; - } - $s.= str_pad('== ('.$n." bytes) ==\n", $p, '=', STR_PAD_LEFT); - $o= 0; - while ($o < $n) { - $s.= sprintf('%04x: ', $o); - for ($i= 0; $i < 16; $i++) { - if ($i + $o >= $n) { - $s.= 7 === $i ? ' ' : ' '; - } else { - $s.= sprintf('%02x %s', ord($bytes{$i + $o}), 7 === $i ? ' ' : ''); - } - } - $o+= $i; - $s.= '|'.str_pad($this->chars($bytes, $o - 16, $o), 16, ' ', STR_PAD_RIGHT)."|\n"; - } - return $s; + $length= $this->encodeLength(strlen($this->write[0]) - 1); + $seq= array_shift($this->write); + $this->write[0].= $seq{0}.$length.substr($seq, 1); } + /** + * Flushes all sequences to output + * + * @return int number of bytes written + */ public function flush() { - \util\cmd\Console::writeLine($this->dump($this->seq[0], '>>>')); - $this->out->write($this->seq[0]); + return $this->out->write($this->write[0]); } + /** + * Read sequence + * + * @param string $tag expected tag + * @return var + */ public function readSequence($tag= self::SEQ_CTOR) { $head= unpack('Ctag/Cl0/a3rest', $this->in->read(5)); if ($head['tag'] !== $tag) { @@ -158,32 +263,32 @@ public function readSequence($tag= self::SEQ_CTOR) { if ($head['l0'] <= 0x7f) { $length= $head['l0']; - $s= $head['rest']; + $this->in->pushBack(substr($head['rest'])); } else if (0x81 === $head['l0']) { $l= unpack('C', $head['rest']); $length= $l[1]; - $s= substr($head['rest'], -2); + $this->in->pushBack(substr($head['rest'], 1)); } else if (0x82 === $head['l0']) { $l= unpack('C2', $head['rest']); $length= $l[1] * 0x100 + $l[2]; - $s= substr($head['rest'], -1); + $this->in->pushBack(substr($head['rest'], 2)); } else if (0x83 === $head['l0']) { $l= unpack('C3', $head['rest']); $length= $l[1] * 0x10000 + $l[2] * 0x100 + $l[3]; - $s= ''; } else { throw new \lang\IllegalStateException('Length too long: '.$head['l0']); } - while (strlen($s) < $length) { - $s.= $this->in->read($length - strlen($s)); - } - return $s; + return ['tag' => $tag, 'length' => $length]; } + /** + * Read response + * + * @return var + */ public function read() { $seq= $this->readSequence(); - \util\cmd\Console::writeLine($this->dump($seq, '<<<')); - return new \lang\types\Bytes($seq); + return $seq; } } \ No newline at end of file From 8e18258eb4de94ef3947c1579b504ab77cb9a33f Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Tue, 6 May 2014 18:49:35 +0200 Subject: [PATCH 03/32] Add badges: XP, BSD licence, PHP 5.4+ --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dfcd30c2..b9b3e5b1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ -LDAP protocol support for the XP Framework +LDAP support for the XP Framework ======================================================================== +[](https://github.com/xp-framework/core) +[](https://github.com/xp-framework/core/blob/master/LICENCE.md) +[](http://php.net/) + The peer.ldap package implements LDAP (Lighweight Directory Access Protocol) access. From 07cebb06fa883c32b8b2e371b3abf58505e63e3a Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Tue, 6 May 2014 21:38:15 +0200 Subject: [PATCH 04/32] First working reader --- .../php/peer/ldap/util/BerStream.class.php | 133 +++++++++++++++--- .../php/peer/ldap/util/LdapProtocol.class.php | 90 +++++++++++- 2 files changed, 194 insertions(+), 29 deletions(-) diff --git a/src/main/php/peer/ldap/util/BerStream.class.php b/src/main/php/peer/ldap/util/BerStream.class.php index ea38e847..8fbe2732 100755 --- a/src/main/php/peer/ldap/util/BerStream.class.php +++ b/src/main/php/peer/ldap/util/BerStream.class.php @@ -40,6 +40,7 @@ class BerStream extends \lang\Object { const SEQ_CTOR = 48; // (SEQUENCE | CONSTRUCTOR) protected $write= ['']; + protected $read= [0]; /** * Constructor @@ -246,49 +247,135 @@ public function endSequence() { * @return int number of bytes written */ public function flush() { - return $this->out->write($this->write[0]); + $w= $this->out->write($this->write[0]); + $this->write= ['']; + return $w; } - /** - * Read sequence - * - * @param string $tag expected tag - * @return var - */ - public function readSequence($tag= self::SEQ_CTOR) { - $head= unpack('Ctag/Cl0/a3rest', $this->in->read(5)); - if ($head['tag'] !== $tag) { - throw new \lang\IllegalStateException(sprintf('Expected %0x, have %0x', $tag, $head['tag'])); + public function read($l) { + $t= debug_backtrace(); + $chunk= $this->in->read($l); + $this->read[0]-= strlen($chunk); + // fprintf(STDOUT, "%s READ %d bytes from %s, remain %d\n", str_repeat(' ', sizeof($this->read)), $l, $t[1]['function'], $this->read[0]); + return $chunk; + } + + public function readTag($expected) { + $head= unpack('Ctag', $this->in->read(1)); + $test= (array)$expected; + if (!in_array($head['tag'], $test)) { + throw new \lang\IllegalStateException(sprintf( + 'Expected any of [%s], have 0x%02x', + implode(', ', array_map(function($t) { return sprintf('0x%02x', $t); }, $test)), + $head['tag'] + )); } + return $head['tag']; + } + + protected function decodeLength() { + $head= unpack('Cl0', $this->in->read(1)); if ($head['l0'] <= 0x7f) { $length= $head['l0']; - $this->in->pushBack(substr($head['rest'])); + $this->read[0]-= 2; } else if (0x81 === $head['l0']) { - $l= unpack('C', $head['rest']); + $l= unpack('C', $this->in->read(1)); $length= $l[1]; - $this->in->pushBack(substr($head['rest'], 1)); + $this->read[0]-= 3; } else if (0x82 === $head['l0']) { - $l= unpack('C2', $head['rest']); + $l= unpack('C2', $this->in->read(2)); $length= $l[1] * 0x100 + $l[2]; - $this->in->pushBack(substr($head['rest'], 2)); + $this->read[0]-= 4; } else if (0x83 === $head['l0']) { - $l= unpack('C3', $head['rest']); + $l= unpack('C3', $this->in->read(3)); $length= $l[1] * 0x10000 + $l[2] * 0x100 + $l[3]; + $this->read[0]-= 5; } else { throw new \lang\IllegalStateException('Length too long: '.$head['l0']); } + return $length; + } + + /** + * Reads an integer + * + * @return int + */ + public function readInt() { + $this->readTag(self::INTEGER); + return unpack('N', str_pad($this->read($this->decodeLength()), 4, "\0", STR_PAD_LEFT))[1]; + } - return ['tag' => $tag, 'length' => $length]; + /** + * Reads an enumeration + * + * @return int + */ + public function readEnumeration() { + $this->readTag(self::ENUMERATION); + return unpack('N', str_pad($this->read($this->decodeLength()), 4, "\0", STR_PAD_LEFT))[1]; + } + + /** + * Reads a string + * + * @return string + */ + public function readString() { + $this->readTag(self::OCTETSTRING); + return $this->read($this->decodeLength()); + } + + /** + * Reads a boolean + * + * @return bool + */ + public function readBoolean() { + $this->readTag(self::BOOLEAN); + return $this->read($this->decodeLength()) ? true : false; + } + + /** + * Read sequence + * + * @param var $tag expected either a tag or an array of tags + * @return int The found tag + */ + public function readSequence($tag= self::SEQ_CTOR) { + $tag= $this->readTag($tag); + $len= $this->decodeLength(); + $this->read[0]-= $len; + array_unshift($this->read, $len); + // fprintf(STDOUT, "%s`- BEGIN SEQ %d bytes\n", str_repeat(' ', sizeof($this->read)), $this->read[0]); + return $tag; + } + + public function remaining() { + return $this->read[0]; + } + + public function available() { + return $this->in->available(); + } + + /** + * Finish reading a sequence + */ + public function finishSequence() { + // fprintf(STDOUT, "%s END SEQ remain: %d bytes\n", str_repeat(' ', sizeof($this->read)), $this->read[0]); + $shift= array_shift($this->read); + $this->read[0]-= $shift; } /** - * Read response + * Closes I/O * - * @return var + * @return void */ - public function read() { - $seq= $this->readSequence(); - return $seq; + public function close() { + $this->in->close(); + $this->out->close(); } } \ No newline at end of file diff --git a/src/main/php/peer/ldap/util/LdapProtocol.class.php b/src/main/php/peer/ldap/util/LdapProtocol.class.php index af2c4821..bf1ac436 100755 --- a/src/main/php/peer/ldap/util/LdapProtocol.class.php +++ b/src/main/php/peer/ldap/util/LdapProtocol.class.php @@ -12,6 +12,17 @@ class LdapProtocol extends \lang\Object { const REQ_ABANDON = 0x50; const REQ_EXTENSION = 0x77; + const REP_BIND = 0x61; + const REP_SEARCH_ENTRY = 0x64; + const REP_SEARCH_REF = 0x73; + const REP_SEARCH = 0x65; + const REP_MODIFY = 0x67; + const REP_ADD = 0x69; + const REP_DELETE = 0x6b; + const REP_MODRDN = 0x6d; + const REP_COMPARE = 0x6f; + const REP_EXTENSION = 0x78; + const SCOPE_BASE_OBJECT = 0; const SCOPE_ONE_LEVEL = 1; const SCOPE_SUBTREE = 2; @@ -37,17 +48,84 @@ protected function nextMessageId() { return $this->messageId; } - public function send($request) { + public function send($message) { $this->stream->startSequence(); $this->stream->writeInt($this->nextMessageId()); - - $this->stream->startSequence($request['op']); - call_user_func($request['write'], $this->stream); + call_user_func($message['write'], $this->stream); $this->stream->endSequence(); - $this->stream->endSequence(); $this->stream->flush(); - return $this->stream->read(); + return call_user_func($message['read'], $this->stream); + } + + public function search() { + static $handlers= null; + + if (!$handlers) $handlers= [ + self::REP_SEARCH_ENTRY => function($stream) { + $name= $stream->readString(); + $stream->readSequence(); + $attributes= []; + do { + $stream->readSequence(); + $attr= $stream->readString(); + + $stream->readSequence(0x31); + $attributes[$attr]= []; + do { + $attributes[$attr][]= $stream->readString(); + } while ($stream->remaining()); + $stream->finishSequence(); + + $stream->finishSequence(); + } while ($stream->remaining()); + $stream->finishSequence(); + return ['name' => $name, 'attr' => $attributes]; + }, + self::REP_SEARCH => function($stream) { + $stream->read($stream->remaining()); // XXX FIXME + return '<EOR>'; + } + ]; + + return $this->send([ + 'write' => function($stream) { + $stream->startSequence(self::REQ_SEARCH); + $stream->writeString('o=example'); + $stream->writeEnumeration(0); + $stream->writeEnumeration(0); + $stream->writeInt(0); + $stream->writeInt(0); + $stream->writeBoolean(false); + + $stream->startSequence(0x87); + $stream->write('objectClass'); + $stream->endSequence(); + + $stream->startSequence(); + $stream->endSequence(); + $stream->endSequence(); + }, + 'read' => function($stream) use($handlers) { + $result= []; + do { + $stream->readSequence(); + $messageId= $stream->readInt(); + + $tag= $stream->readSequence([self::REP_SEARCH_ENTRY, self::REP_SEARCH]); + $result[]= call_user_func($handlers[$tag], $stream); + $stream->finishSequence(); + + $stream->finishSequence(); + } while (self::REP_SEARCH_ENTRY === $tag); + + return $result; + } + ]); + } + + public function __destruct() { + $this->stream->close(); } } \ No newline at end of file From cf34029d99cf39daebe3bfa4c52f09d00c2719cc Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Tue, 6 May 2014 21:48:30 +0200 Subject: [PATCH 05/32] Move handling of multiple results into send() --- .../php/peer/ldap/util/LdapProtocol.class.php | 94 +++++++++---------- 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/src/main/php/peer/ldap/util/LdapProtocol.class.php b/src/main/php/peer/ldap/util/LdapProtocol.class.php index bf1ac436..38434efb 100755 --- a/src/main/php/peer/ldap/util/LdapProtocol.class.php +++ b/src/main/php/peer/ldap/util/LdapProtocol.class.php @@ -32,6 +32,10 @@ class LdapProtocol extends \lang\Object { const DEREF_BASE_OBJECT = 2; const DEREF_ALWAYS = 3; + protected static $continue= [ + self::REP_SEARCH_ENTRY => true + ]; + protected $messageId= 0; public function __construct(\peer\Socket $sock) { @@ -49,49 +53,32 @@ protected function nextMessageId() { } public function send($message) { - $this->stream->startSequence(); - $this->stream->writeInt($this->nextMessageId()); - call_user_func($message['write'], $this->stream); - $this->stream->endSequence(); - + with ($this->stream->startSequence()); { + $this->stream->writeInt($this->nextMessageId()); + $this->stream->startSequence($message['req']); + call_user_func($message['write'], $this->stream); + $this->stream->endSequence(); + $this->stream->endSequence(); + } $this->stream->flush(); - return call_user_func($message['read'], $this->stream); + $result= []; + do { + with ($this->stream->readSequence()); { + $messageId= $this->stream->readInt(); + $tag= $this->stream->readSequence($message['rep']); + $result[]= call_user_func($message['read'][$tag], $this->stream); + $this->stream->finishSequence(); + $this->stream->finishSequence(); + } + } while (isset(self::$continue[$tag])); + return $result; } public function search() { - static $handlers= null; - - if (!$handlers) $handlers= [ - self::REP_SEARCH_ENTRY => function($stream) { - $name= $stream->readString(); - $stream->readSequence(); - $attributes= []; - do { - $stream->readSequence(); - $attr= $stream->readString(); - - $stream->readSequence(0x31); - $attributes[$attr]= []; - do { - $attributes[$attr][]= $stream->readString(); - } while ($stream->remaining()); - $stream->finishSequence(); - - $stream->finishSequence(); - } while ($stream->remaining()); - $stream->finishSequence(); - return ['name' => $name, 'attr' => $attributes]; - }, - self::REP_SEARCH => function($stream) { - $stream->read($stream->remaining()); // XXX FIXME - return '<EOR>'; - } - ]; - return $this->send([ + 'req' => self::REQ_SEARCH, 'write' => function($stream) { - $stream->startSequence(self::REQ_SEARCH); $stream->writeString('o=example'); $stream->writeEnumeration(0); $stream->writeEnumeration(0); @@ -105,23 +92,34 @@ public function search() { $stream->startSequence(); $stream->endSequence(); - $stream->endSequence(); }, - 'read' => function($stream) use($handlers) { - $result= []; - do { + 'rep' => [self::REP_SEARCH_ENTRY, self::REP_SEARCH], + 'read' => [ + self::REP_SEARCH_ENTRY => function($stream) { + $name= $stream->readString(); $stream->readSequence(); - $messageId= $stream->readInt(); + $attributes= []; + do { + $stream->readSequence(); + $attr= $stream->readString(); - $tag= $stream->readSequence([self::REP_SEARCH_ENTRY, self::REP_SEARCH]); - $result[]= call_user_func($handlers[$tag], $stream); - $stream->finishSequence(); + $stream->readSequence(0x31); + $attributes[$attr]= []; + do { + $attributes[$attr][]= $stream->readString(); + } while ($stream->remaining()); + $stream->finishSequence(); + $stream->finishSequence(); + } while ($stream->remaining()); $stream->finishSequence(); - } while (self::REP_SEARCH_ENTRY === $tag); - - return $result; - } + return ['name' => $name, 'attr' => $attributes]; + }, + self::REP_SEARCH => function($stream) { + $stream->read($stream->remaining()); // XXX FIXME + return '<EOR>'; + } + ] ]); } From e584c3a33384e9ad3588c99d28a6f8fd549d1813 Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Tue, 6 May 2014 21:52:03 +0200 Subject: [PATCH 06/32] Make in and out members protected --- src/main/php/peer/ldap/util/BerStream.class.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/php/peer/ldap/util/BerStream.class.php b/src/main/php/peer/ldap/util/BerStream.class.php index 8fbe2732..e91f7862 100755 --- a/src/main/php/peer/ldap/util/BerStream.class.php +++ b/src/main/php/peer/ldap/util/BerStream.class.php @@ -42,6 +42,8 @@ class BerStream extends \lang\Object { protected $write= ['']; protected $read= [0]; + protected $in, $out; + /** * Constructor * From 84ccf07bde0c41ec7c3d8b531c3afbe5716a8282 Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Tue, 6 May 2014 22:09:09 +0200 Subject: [PATCH 07/32] Refactor: REP -> RES (better abbreviation for response) --- .../php/peer/ldap/util/LdapProtocol.class.php | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/php/peer/ldap/util/LdapProtocol.class.php b/src/main/php/peer/ldap/util/LdapProtocol.class.php index 38434efb..ae4fc2ed 100755 --- a/src/main/php/peer/ldap/util/LdapProtocol.class.php +++ b/src/main/php/peer/ldap/util/LdapProtocol.class.php @@ -12,16 +12,16 @@ class LdapProtocol extends \lang\Object { const REQ_ABANDON = 0x50; const REQ_EXTENSION = 0x77; - const REP_BIND = 0x61; - const REP_SEARCH_ENTRY = 0x64; - const REP_SEARCH_REF = 0x73; - const REP_SEARCH = 0x65; - const REP_MODIFY = 0x67; - const REP_ADD = 0x69; - const REP_DELETE = 0x6b; - const REP_MODRDN = 0x6d; - const REP_COMPARE = 0x6f; - const REP_EXTENSION = 0x78; + const RES_BIND = 0x61; + const RES_SEARCH_ENTRY = 0x64; + const RES_SEARCH_REF = 0x73; + const RES_SEARCH = 0x65; + const RES_MODIFY = 0x67; + const RES_ADD = 0x69; + const RES_DELETE = 0x6b; + const RES_MODRDN = 0x6d; + const RES_COMPARE = 0x6f; + const RES_EXTENSION = 0x78; const SCOPE_BASE_OBJECT = 0; const SCOPE_ONE_LEVEL = 1; @@ -33,7 +33,7 @@ class LdapProtocol extends \lang\Object { const DEREF_ALWAYS = 3; protected static $continue= [ - self::REP_SEARCH_ENTRY => true + self::RES_SEARCH_ENTRY => true ]; protected $messageId= 0; @@ -66,7 +66,7 @@ public function send($message) { do { with ($this->stream->readSequence()); { $messageId= $this->stream->readInt(); - $tag= $this->stream->readSequence($message['rep']); + $tag= $this->stream->readSequence($message['res']); $result[]= call_user_func($message['read'][$tag], $this->stream); $this->stream->finishSequence(); $this->stream->finishSequence(); @@ -93,9 +93,9 @@ public function search() { $stream->startSequence(); $stream->endSequence(); }, - 'rep' => [self::REP_SEARCH_ENTRY, self::REP_SEARCH], + 'res' => [self::RES_SEARCH_ENTRY, self::RES_SEARCH], 'read' => [ - self::REP_SEARCH_ENTRY => function($stream) { + self::RES_SEARCH_ENTRY => function($stream) { $name= $stream->readString(); $stream->readSequence(); $attributes= []; @@ -115,7 +115,7 @@ public function search() { $stream->finishSequence(); return ['name' => $name, 'attr' => $attributes]; }, - self::REP_SEARCH => function($stream) { + self::RES_SEARCH => function($stream) { $stream->read($stream->remaining()); // XXX FIXME return '<EOR>'; } From 2cb998ac2e87997cc88c1e56088baf8aee1a6f40 Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Tue, 6 May 2014 22:10:24 +0200 Subject: [PATCH 08/32] Make base passable to search() as parameter --- src/main/php/peer/ldap/util/LdapProtocol.class.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/php/peer/ldap/util/LdapProtocol.class.php b/src/main/php/peer/ldap/util/LdapProtocol.class.php index ae4fc2ed..9304ca16 100755 --- a/src/main/php/peer/ldap/util/LdapProtocol.class.php +++ b/src/main/php/peer/ldap/util/LdapProtocol.class.php @@ -75,11 +75,11 @@ public function send($message) { return $result; } - public function search() { + public function search($base) { return $this->send([ 'req' => self::REQ_SEARCH, - 'write' => function($stream) { - $stream->writeString('o=example'); + 'write' => function($stream) use($base) { + $stream->writeString($base); $stream->writeEnumeration(0); $stream->writeEnumeration(0); $stream->writeInt(0); From cd08bb230112b6f67a967855d17a041d14cbec73 Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Wed, 7 May 2014 11:59:55 +0200 Subject: [PATCH 09/32] Fill in enums --- src/main/php/peer/ldap/util/LdapProtocol.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/php/peer/ldap/util/LdapProtocol.class.php b/src/main/php/peer/ldap/util/LdapProtocol.class.php index 9304ca16..769c5a5b 100755 --- a/src/main/php/peer/ldap/util/LdapProtocol.class.php +++ b/src/main/php/peer/ldap/util/LdapProtocol.class.php @@ -80,8 +80,8 @@ public function search($base) { 'req' => self::REQ_SEARCH, 'write' => function($stream) use($base) { $stream->writeString($base); - $stream->writeEnumeration(0); - $stream->writeEnumeration(0); + $stream->writeEnumeration(self::SCOPE_ONE_LEVEL); + $stream->writeEnumeration(self::NEVER_DEREF_ALIASES); $stream->writeInt(0); $stream->writeInt(0); $stream->writeBoolean(false); From dcf9de04e0258e316ce1bd1258b90c3b6be55d4d Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Thu, 8 May 2014 12:46:28 +0200 Subject: [PATCH 10/32] QA: Add api documentation to all methods --- .../php/peer/ldap/util/BerStream.class.php | 2 ++ .../php/peer/ldap/util/LdapProtocol.class.php | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/main/php/peer/ldap/util/BerStream.class.php b/src/main/php/peer/ldap/util/BerStream.class.php index e91f7862..a9b23a55 100755 --- a/src/main/php/peer/ldap/util/BerStream.class.php +++ b/src/main/php/peer/ldap/util/BerStream.class.php @@ -53,6 +53,7 @@ class BerStream extends \lang\Object { public function __construct(InputStream $in, OutputStream $out) { // Debug + /* $in= newinstance('io.streams.InputStream', [$in], [ 'backing' => null, '__construct' => function($backing) { $this->backing= $backing; }, @@ -76,6 +77,7 @@ public function __construct(InputStream $in, OutputStream $out) { 'flush' => function() { return $this->backing->flush(); }, 'close' => function() { $this->backing->close(); } ]); + */ $this->in= $in instanceof BufferedInputStream ? $in : new BufferedInputStream($in); $this->out= $out; diff --git a/src/main/php/peer/ldap/util/LdapProtocol.class.php b/src/main/php/peer/ldap/util/LdapProtocol.class.php index 769c5a5b..5e6ff1bb 100755 --- a/src/main/php/peer/ldap/util/LdapProtocol.class.php +++ b/src/main/php/peer/ldap/util/LdapProtocol.class.php @@ -38,6 +38,11 @@ class LdapProtocol extends \lang\Object { protected $messageId= 0; + /** + * Creates a new protocol instance communicating on the given socket + * + * @param peer.Socket $sock + */ public function __construct(\peer\Socket $sock) { $this->stream= new BerStream( $sock->getInputStream(), @@ -45,6 +50,11 @@ public function __construct(\peer\Socket $sock) { ); } + /** + * Calculates and returns next message id, starting with 1. + * + * @return int + */ protected function nextMessageId() { if (++$this->messageId >= 0x7fffffff) { $this->messageId= 1; @@ -52,6 +62,12 @@ protected function nextMessageId() { return $this->messageId; } + /** + * Send message, return result + * + * @param var $message + * @return var + */ public function send($message) { with ($this->stream->startSequence()); { $this->stream->writeInt($this->nextMessageId()); @@ -75,6 +91,12 @@ public function send($message) { return $result; } + /** + * Search + * + * @param string $base + * @return var + */ public function search($base) { return $this->send([ 'req' => self::REQ_SEARCH, From bc599462bad911f079c784202d08cccf360d9471 Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Thu, 8 May 2014 13:04:44 +0200 Subject: [PATCH 11/32] Use larger default buffer for reading --- src/main/php/peer/ldap/util/BerStream.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/php/peer/ldap/util/BerStream.class.php b/src/main/php/peer/ldap/util/BerStream.class.php index a9b23a55..3c0b41dd 100755 --- a/src/main/php/peer/ldap/util/BerStream.class.php +++ b/src/main/php/peer/ldap/util/BerStream.class.php @@ -79,7 +79,7 @@ public function __construct(InputStream $in, OutputStream $out) { ]); */ - $this->in= $in instanceof BufferedInputStream ? $in : new BufferedInputStream($in); + $this->in= $in instanceof BufferedInputStream ? $in : new BufferedInputStream($in, 8192); $this->out= $out; } From a6366971a132c1a88cd83727afeec02fa51a0d4d Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Thu, 8 May 2014 13:05:03 +0200 Subject: [PATCH 12/32] Implement bind operation --- .../php/peer/ldap/util/LdapProtocol.class.php | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/main/php/peer/ldap/util/LdapProtocol.class.php b/src/main/php/peer/ldap/util/LdapProtocol.class.php index 5e6ff1bb..379a351c 100755 --- a/src/main/php/peer/ldap/util/LdapProtocol.class.php +++ b/src/main/php/peer/ldap/util/LdapProtocol.class.php @@ -1,5 +1,7 @@ <?php namespace peer\ldap\util; +use peer\ldap\LDAPException; + class LdapProtocol extends \lang\Object { const REQ_BIND = 0x60; const REQ_UNBIND = 0x42; @@ -32,6 +34,8 @@ class LdapProtocol extends \lang\Object { const DEREF_BASE_OBJECT = 2; const DEREF_ALWAYS = 3; + const STATUS_OK = 0; + protected static $continue= [ self::RES_SEARCH_ENTRY => true ]; @@ -91,6 +95,38 @@ public function send($message) { return $result; } + + /** + * Bind + * + * @param string $user + * @param string $password + */ + public function bind($user, $password) { + $result= $this->send([ + 'req' => self::REQ_BIND, + 'write' => function($stream) use($user, $password) { + $stream->writeInt($version= 3); + $stream->writeString($user); + $stream->writeString($password, BerStream::CONTEXT); + }, + 'res' => self::RES_BIND, + 'read' => [self::RES_BIND => function($stream) { + $status= $stream->readEnumeration(); + $matchedDN= $stream->readString(); + $error= $stream->readString(); + + // TODO: Referalls + $stream->read($stream->remaining()); + return ['status' => $status, 'matchedDN' => $matchedDN, 'error' => $error]; + }] + ])[0]; + \util\cmd\Console::writeLine($result); + if (self::STATUS_OK === $result['status']) return true; + + throw new LDAPException($result['error'] ?: 'Bind error', $result['status']); + } + /** * Search * From fab460be3a08fea950c2a99d17552363494cf519 Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Thu, 8 May 2014 16:50:04 +0200 Subject: [PATCH 13/32] Document bind() returns void, throws an LDAPException --- src/main/php/peer/ldap/util/LdapProtocol.class.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/php/peer/ldap/util/LdapProtocol.class.php b/src/main/php/peer/ldap/util/LdapProtocol.class.php index 379a351c..3440c785 100755 --- a/src/main/php/peer/ldap/util/LdapProtocol.class.php +++ b/src/main/php/peer/ldap/util/LdapProtocol.class.php @@ -101,6 +101,8 @@ public function send($message) { * * @param string $user * @param string $password + * @return void + * @throws peer.ldap.LDAPException */ public function bind($user, $password) { $result= $this->send([ @@ -122,9 +124,9 @@ public function bind($user, $password) { }] ])[0]; \util\cmd\Console::writeLine($result); - if (self::STATUS_OK === $result['status']) return true; - - throw new LDAPException($result['error'] ?: 'Bind error', $result['status']); + if (self::STATUS_OK !== $result['status']) { + throw new LDAPException($result['error'] ?: 'Bind error', $result['status']); + } } /** From 751ee120ef518adaaea268bba7aeeb23b11512ac Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Thu, 8 May 2014 16:50:23 +0200 Subject: [PATCH 14/32] QA: WS --- src/main/php/peer/ldap/util/LdapProtocol.class.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/php/peer/ldap/util/LdapProtocol.class.php b/src/main/php/peer/ldap/util/LdapProtocol.class.php index 3440c785..169e2973 100755 --- a/src/main/php/peer/ldap/util/LdapProtocol.class.php +++ b/src/main/php/peer/ldap/util/LdapProtocol.class.php @@ -95,7 +95,6 @@ public function send($message) { return $result; } - /** * Bind * From 1d2bb2eeceeaee4be2bb583b05aabfd69011780b Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Thu, 8 May 2014 18:16:22 +0200 Subject: [PATCH 15/32] Throw exceptions if search failed --- .../php/peer/ldap/util/LdapProtocol.class.php | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/main/php/peer/ldap/util/LdapProtocol.class.php b/src/main/php/peer/ldap/util/LdapProtocol.class.php index 169e2973..37155ffc 100755 --- a/src/main/php/peer/ldap/util/LdapProtocol.class.php +++ b/src/main/php/peer/ldap/util/LdapProtocol.class.php @@ -87,7 +87,14 @@ public function send($message) { with ($this->stream->readSequence()); { $messageId= $this->stream->readInt(); $tag= $this->stream->readSequence($message['res']); - $result[]= call_user_func($message['read'][$tag], $this->stream); + try { + $result[]= call_user_func($message['read'][$tag], $this->stream); + } catch (\lang\XPException $e) { + $this->stream->finishSequence(); + $this->stream->finishSequence(); + $this->stream->read($this->stream->remaining()); + throw $e; + } $this->stream->finishSequence(); $this->stream->finishSequence(); } @@ -104,7 +111,7 @@ public function send($message) { * @throws peer.ldap.LDAPException */ public function bind($user, $password) { - $result= $this->send([ + $this->send([ 'req' => self::REQ_BIND, 'write' => function($stream) use($user, $password) { $stream->writeInt($version= 3); @@ -119,13 +126,11 @@ public function bind($user, $password) { // TODO: Referalls $stream->read($stream->remaining()); - return ['status' => $status, 'matchedDN' => $matchedDN, 'error' => $error]; + if (self::STATUS_OK !== $status) { + throw new LDAPException($error ?: 'Bind error', $status); + } }] - ])[0]; - \util\cmd\Console::writeLine($result); - if (self::STATUS_OK !== $result['status']) { - throw new LDAPException($result['error'] ?: 'Bind error', $result['status']); - } + ]); } /** @@ -175,8 +180,13 @@ public function search($base) { return ['name' => $name, 'attr' => $attributes]; }, self::RES_SEARCH => function($stream) { - $stream->read($stream->remaining()); // XXX FIXME - return '<EOR>'; + $status= $stream->readEnumeration(); + $matchedDN= $stream->readString(); + $error= $stream->readString(); + + if (self::STATUS_OK !== $status) { + throw new LDAPException($error ?: 'Search failed', $status); + } } ] ]); From 4207884619ff731a4fc286bdbd9373389dee0c74 Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Thu, 8 May 2014 18:18:06 +0200 Subject: [PATCH 16/32] Add explicit close() method --- src/main/php/peer/ldap/util/LdapProtocol.class.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/php/peer/ldap/util/LdapProtocol.class.php b/src/main/php/peer/ldap/util/LdapProtocol.class.php index 37155ffc..600abd7e 100755 --- a/src/main/php/peer/ldap/util/LdapProtocol.class.php +++ b/src/main/php/peer/ldap/util/LdapProtocol.class.php @@ -192,7 +192,17 @@ public function search($base) { ]); } - public function __destruct() { + /** + * Closes the connection + */ + public function close() { $this->stream->close(); } + + /** + * Destructor. Ensures stream is closed. + */ + public function __destruct() { + $this->close(); + } } \ No newline at end of file From 4370bf065baf0a434d516d3423fdafcaa7d33ceb Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Thu, 8 May 2014 18:27:01 +0200 Subject: [PATCH 17/32] Refactor: Extract response handling into helper method --- .../php/peer/ldap/util/LdapProtocol.class.php | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/php/peer/ldap/util/LdapProtocol.class.php b/src/main/php/peer/ldap/util/LdapProtocol.class.php index 600abd7e..dda5a059 100755 --- a/src/main/php/peer/ldap/util/LdapProtocol.class.php +++ b/src/main/php/peer/ldap/util/LdapProtocol.class.php @@ -66,6 +66,18 @@ protected function nextMessageId() { return $this->messageId; } + /** + * Handle response + * + * @param int $status + * @param sting $error + */ + protected function handleResponse($status, $error= null) { + if (self::STATUS_OK !== $status) { + throw new LDAPException($error, $status); + } + } + /** * Send message, return result * @@ -125,10 +137,8 @@ public function bind($user, $password) { $error= $stream->readString(); // TODO: Referalls - $stream->read($stream->remaining()); - if (self::STATUS_OK !== $status) { - throw new LDAPException($error ?: 'Bind error', $status); - } + + $this->handleResponse($status, $error ?: 'Bind error'); }] ]); } @@ -184,9 +194,7 @@ public function search($base) { $matchedDN= $stream->readString(); $error= $stream->readString(); - if (self::STATUS_OK !== $status) { - throw new LDAPException($error ?: 'Search failed', $status); - } + $this->handleResponse($status, $error ?: 'Search failed'); } ] ]); From c5391a68222b9e55cdb487ba61f78e1971742ee9 Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Mon, 30 Jul 2018 12:18:15 +0200 Subject: [PATCH 18/32] Remove lang.Object base class --- src/main/php/peer/ldap/util/BerStream.class.php | 2 +- src/main/php/peer/ldap/util/LdapProtocol.class.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/php/peer/ldap/util/BerStream.class.php b/src/main/php/peer/ldap/util/BerStream.class.php index 3c0b41dd..ddb1f6f0 100755 --- a/src/main/php/peer/ldap/util/BerStream.class.php +++ b/src/main/php/peer/ldap/util/BerStream.class.php @@ -4,7 +4,7 @@ use io\streams\InputStream; use io\streams\OutputStream; -class BerStream extends \lang\Object { +class BerStream { const EOC = 0; const BOOLEAN = 1; const INTEGER = 2; diff --git a/src/main/php/peer/ldap/util/LdapProtocol.class.php b/src/main/php/peer/ldap/util/LdapProtocol.class.php index dda5a059..21295073 100755 --- a/src/main/php/peer/ldap/util/LdapProtocol.class.php +++ b/src/main/php/peer/ldap/util/LdapProtocol.class.php @@ -2,7 +2,7 @@ use peer\ldap\LDAPException; -class LdapProtocol extends \lang\Object { +class LdapProtocol { const REQ_BIND = 0x60; const REQ_UNBIND = 0x42; const REQ_SEARCH = 0x63; From 518e09083def5d2dad2a7ec25c24244c48301133 Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Fri, 17 Aug 2018 00:58:53 +0200 Subject: [PATCH 19/32] Extract search(), connect(), bind() and close() to library class --- .../php/peer/ldap/LDAPConnection.class.php | 179 ++++++------------ .../php/peer/ldap/util/LdapLibrary.class.php | 153 +++++++++++++++ 2 files changed, 216 insertions(+), 116 deletions(-) create mode 100755 src/main/php/peer/ldap/util/LdapLibrary.class.php diff --git a/src/main/php/peer/ldap/LDAPConnection.class.php b/src/main/php/peer/ldap/LDAPConnection.class.php index 0257216e..dec8b44c 100755 --- a/src/main/php/peer/ldap/LDAPConnection.class.php +++ b/src/main/php/peer/ldap/LDAPConnection.class.php @@ -1,9 +1,12 @@ <?php namespace peer\ldap; +use lang\IllegalArgumentException; +use lang\Value; +use lang\XPClass; use peer\ConnectException; use peer\URL; -use lang\XPClass; -use lang\IllegalArgumentException; +use peer\ldap\util\LdapLibrary; +use peer\ldap\util\LdapProtocol; use util\Secret; /** @@ -23,31 +26,12 @@ * @ext ldap * @test xp://peer.ldap.unittest.LDAPConnectionTest */ -class LDAPConnection { - private static $options; - +class LDAPConnection implements Value { private $url; private $handle= null; static function __static() { XPClass::forName('peer.ldap.LDAPException'); // Error codes - self::$options= [ - 'deref' => function($handle, $value) { - return ldap_set_option($handle, LDAP_OPT_DEREF, constant('LDAP_DEREF_'.strtoupper($value))); - }, - 'sizelimit' => function($handle, $value) { - return ldap_set_option($handle, LDAP_OPT_SIZELIMIT, (int)$value); - }, - 'timelimit' => function($handle, $value) { - return ldap_set_option($handle, LDAP_OPT_TIMELIMIT, (int)$value); - }, - 'network_timeout' => function($handle, $value) { - return ldap_set_option($handle, LDAP_OPT_NETWORK_TIMEOUT, (int)$value); - }, - 'protocol_version' => function($handle, $value) { - return ldap_set_option($handle, LDAP_OPT_PROTOCOL_VERSION, (int)$value); - }, - ]; } /** @@ -58,12 +42,18 @@ static function __static() { * @throws lang.IllegalArgumentException when DSN is malformed */ public function __construct($dsn) { + static $ports= ['ldap' => 389, 'ldaps' => 636]; + + // TODO: Driver! + $impl= getenv('PROTO') ? LdapProtocol::class : LdapLibrary::class; + $this->url= $dsn instanceof URL ? $dsn : new URL($dsn); - foreach ($this->url->getParams() as $option => $value) { - if (!isset(self::$options[$option])) { - throw new IllegalArgumentException('Unknown option "'.$option.'"'); - } - } + $this->proto= new $impl( + $this->url->getScheme(), + $this->url->getHost(), + $this->url->getPort($ports[$this->url->getScheme()]), + $this->url->getParams() + ); } /** @return peer.URL */ @@ -79,45 +69,12 @@ public function dsn() { return $this->url; } * @throws peer.ConnectException */ public function connect($dn= null, Secret $password= null) { - static $ports= ['ldap' => 389, 'ldaps' => 636]; - - if ($this->isConnected()) return true; + if ($this->proto->connected()) return true; - $uri= sprintf( - '%s://%s:%d', - $this->url->getScheme(), - $this->url->getHost(), - $this->url->getPort($ports[$this->url->getScheme()]) + $this->proto->connect( + $dn ?: $this->url->getUser(null), + $password ?: new Secret($this->url->getPassword(null)) ); - if (false === ($this->handle= ldap_connect($uri))) { - throw new ConnectException('Cannot connect to '.$uri); - } - - foreach (array_merge(['protocol_version' => 3], $this->url->getParams()) as $option => $value) { - $set= self::$options[$option]; - if (!$set($this->handle, $value)) { - ldap_unbind($this->handle); - $this->handle= null; - throw new LDAPException('Cannot set option "'.$option.'"', ldap_errno($this->handle)); - } - } - - if (null === $dn) { - $result= ldap_bind($this->handle, $this->url->getUser(null), $this->url->getPassword(null)); - } else { - $result= ldap_bind($this->handle, $dn, $password->reveal()); - } - if (false === $result) { - $error= ldap_errno($this->handle); - ldap_unbind($this->handle); - $this->handle= null; - if (LDAP_SERVER_DOWN === $error || -1 === $error) { - throw new ConnectException('Cannot connect to '.$uri); - } else { - throw new LDAPException('Cannot bind for "'.($dn ?: $this->url->getUser(null)).'"', $error); - } - } - return $this; } @@ -127,7 +84,7 @@ public function connect($dn= null, Secret $password= null) { * @return bool */ public function isConnected() { - return is_resource($this->handle); + return $this->proto->connected(); } /** @@ -136,10 +93,7 @@ public function isConnected() { * @see php://ldap_close */ public function close() { - if ($this->handle) { - ldap_unbind($this->handle); - $this->handle= null; - } + $this->proto->close(); } /** @@ -149,11 +103,11 @@ public function close() { * @return peer.ldap.LDAPException */ private function error($message) { - $error= ldap_errno($this->handle); + $error= ldap_errno($this->proto->handle); switch ($error) { case -1: case LDAP_SERVER_DOWN: - ldap_unbind($this->handle); - $this->handle= null; + ldap_unbind($this->proto->handle); + $this->proto->handle= null; return new LDAPDisconnected($message, $error); case LDAP_NO_SUCH_OBJECT: @@ -179,20 +133,17 @@ private function error($message) { * @see php://ldap_search */ public function search($base, $filter, $attributes= [], $attrsOnly= 0, $sizeLimit= 0, $timeLimit= 0, $deref= LDAP_DEREF_NEVER) { - if (false === ($res= ldap_search( - $this->handle, + return $this->proto->search( + LDAPQuery::SCOPE_SUB, $base, $filter, $attributes, $attrsOnly, $sizeLimit, $timeLimit, + null, $deref - ))) { - throw $this->error('Search failed'); - } - - return new LDAPSearchResult(new LDAPEntries($this->handle, $res)); + ); } /** @@ -202,37 +153,17 @@ public function search($base, $filter, $attributes= [], $attrsOnly= 0, $sizeLimi * @return peer.ldap.LDAPSearchResult search result object */ public function searchBy(LDAPQuery $filter) { - static $methods= [ - LDAPQuery::SCOPE_BASE => 'ldap_read', - LDAPQuery::SCOPE_ONELEVEL => 'ldap_list', - LDAPQuery::SCOPE_SUB => 'ldap_search' - ]; - - if (!isset($methods[$filter->getScope()])) { - throw new IllegalArgumentException('Scope '.$filter->getScope().' not supported'); - } - - $f= $methods[$filter->getScope()]; - if (false === ($res= $f( - $this->handle, + return $this->proto->search( + $filter->getScope(), $filter->getBase(), $filter->getFilter(), $filter->getAttrs(), $filter->getAttrsOnly(), $filter->getSizeLimit(), $filter->getTimelimit(), + $filter->getSort(), $filter->getDeref() - ))) { - throw $this->error('Search failed'); - } - - if ($sort= $filter->getSort()) { - $entries= new SortedLDAPEntries($this->handle, $res, $sort); - } else { - $entries= new LDAPEntries($this->handle, $res); - } - - return new LDAPSearchResult($entries); + ); } /** @@ -244,13 +175,13 @@ public function searchBy(LDAPQuery $filter) { * @throws peer.ldap.LDAPException */ public function read(LDAPEntry $entry) { - $res= ldap_read($this->handle, $entry->getDN(), 'objectClass=*', [], false, 0); - if (LDAP_SUCCESS != ldap_errno($this->handle)) { + $res= ldap_read($this->proto->handle, $entry->getDN(), 'objectClass=*', [], false, 0); + if (LDAP_SUCCESS != ldap_errno($this->proto->handle)) { throw $this->error('Read "'.$entry->getDN().'" failed'); } - $entry= ldap_first_entry($this->handle, $res); - return LDAPEntry::create(ldap_get_dn($this->handle, $entry), ldap_get_attributes($this->handle, $entry)); + $entry= ldap_first_entry($this->proto->handle, $res); + return LDAPEntry::create(ldap_get_dn($this->proto->handle, $entry), ldap_get_attributes($this->proto->handle, $entry)); } /** @@ -260,15 +191,15 @@ public function read(LDAPEntry $entry) { * @return bool TRUE if the entry exists */ public function exists(LDAPEntry $entry) { - $res= ldap_read($this->handle, $entry->getDN(), 'objectClass=*', [], false, 0); + $res= ldap_read($this->proto->handle, $entry->getDN(), 'objectClass=*', [], false, 0); // Check for certain error code (#32) - if (LDAP_NO_SUCH_OBJECT === ldap_errno($this->handle)) { + if (LDAP_NO_SUCH_OBJECT === ldap_errno($this->proto->handle)) { return false; } // Check for other errors - if (LDAP_SUCCESS != ldap_errno($this->handle)) { + if (LDAP_SUCCESS != ldap_errno($this->proto->handle)) { throw $this->error('Read "'.$entry->getDN().'" failed'); } @@ -289,7 +220,7 @@ public function add(LDAPEntry $entry) { // This actually returns NULL on failure, not FALSE, as documented if (null == ($res= ldap_add( - $this->handle, + $this->proto->handle, $entry->getDN(), $entry->getAttributes() ))) { @@ -312,7 +243,7 @@ public function add(LDAPEntry $entry) { */ public function modify(LDAPEntry $entry) { if (false == ($res= ldap_modify( - $this->handle, + $this->proto->handle, $entry->getDN(), $entry->getAttributes() ))) { @@ -332,7 +263,7 @@ public function modify(LDAPEntry $entry) { */ public function delete(LDAPEntry $entry) { if (false == ($res= ldap_delete( - $this->handle, + $this->proto->handle, $entry->getDN() ))) { throw $this->error('Delete for "'.$entry->getDN().'" failed'); @@ -351,7 +282,7 @@ public function delete(LDAPEntry $entry) { */ public function addAttribute(LDAPEntry $entry, $name, $value) { if (false == ($res= ldap_mod_add( - $this->handle, + $this->proto->handle, $entry->getDN(), [$name => $value] ))) { @@ -370,7 +301,7 @@ public function addAttribute(LDAPEntry $entry, $name, $value) { */ public function deleteAttribute(LDAPEntry $entry, $name) { if (false == ($res= ldap_mod_del( - $this->handle, + $this->proto->handle, $entry->getDN(), $name ))) { @@ -390,7 +321,7 @@ public function deleteAttribute(LDAPEntry $entry, $name) { */ public function replaceAttribute(LDAPEntry $entry, $name, $value) { if (false == ($res= ldap_mod_replace( - $this->handle, + $this->proto->handle, $entry->getDN(), [$name => $value] ))) { @@ -399,4 +330,20 @@ public function replaceAttribute(LDAPEntry $entry, $name, $value) { return $res; } + + /** @return string */ + public function toString() { return nameof($this).'('.$this->proto->connection().')'; } + + /** @return string */ + public function hashCode() { return 'C'.$this->proto->id(); } + + /** + * Compare + * + * @param var $value + * @return int + */ + public function compareTo($value) { + return $value === $this ? 0 : 1; + } } \ No newline at end of file diff --git a/src/main/php/peer/ldap/util/LdapLibrary.class.php b/src/main/php/peer/ldap/util/LdapLibrary.class.php new file mode 100755 index 00000000..cca6e5b9 --- /dev/null +++ b/src/main/php/peer/ldap/util/LdapLibrary.class.php @@ -0,0 +1,153 @@ +<?php namespace peer\ldap\util; + +use lang\IllegalArgumentException; +use peer\ConnectException; +use peer\ldap\LDAPDisconnected; +use peer\ldap\LDAPEntries; +use peer\ldap\LDAPException; +use peer\ldap\LDAPNoSuchObject; +use peer\ldap\LDAPQuery; +use peer\ldap\LDAPSearchResult; +use peer\ldap\SortedLDAPEntries; + +class LdapLibrary { + private static $options; + private $url; + public $handle= null; + + static function __static() { + self::$options= [ + 'deref' => function($handle, $value) { + return ldap_set_option($handle, LDAP_OPT_DEREF, constant('LDAP_DEREF_'.strtoupper($value))); + }, + 'sizelimit' => function($handle, $value) { + return ldap_set_option($handle, LDAP_OPT_SIZELIMIT, (int)$value); + }, + 'timelimit' => function($handle, $value) { + return ldap_set_option($handle, LDAP_OPT_TIMELIMIT, (int)$value); + }, + 'network_timeout' => function($handle, $value) { + return ldap_set_option($handle, LDAP_OPT_NETWORK_TIMEOUT, (int)$value); + }, + 'protocol_version' => function($handle, $value) { + return ldap_set_option($handle, LDAP_OPT_PROTOCOL_VERSION, (int)$value); + }, + ]; + } + + public function __construct($scheme, $host, $port, $params) { + $this->uri= sprintf('%s://%s:%d', $scheme, $host, $port); + foreach ($params as $option => $value) { + if (!isset(self::$options[$option])) { + throw new IllegalArgumentException('Unknown option "'.$option.'"'); + } + } + $this->params= array_merge(['protocol_version' => 3], $params); + } + + /** + * Error handler + * + * @param string $message + * @return peer.ldap.LDAPException + */ + private function error($message) { + $error= ldap_errno($this->proto->handle); + switch ($error) { + case -1: case LDAP_SERVER_DOWN: + ldap_unbind($this->proto->handle); + $this->proto->handle= null; + return new LDAPDisconnected($message, $error); + + case LDAP_NO_SUCH_OBJECT: + return new LDAPNoSuchObject($message, $error); + + default: + return new LDAPException($message, $error); + } + } + + /** @return string */ + public function connection() { return $this->handle.' -> '.$this->uri; } + + /** @return int */ + public function id() { return (int)$this->handle; } + + /** @return bool */ + public function connected() { return null !== $this->handle; } + + /** + * Connect and bind + * + * @param string $user + * @param util.Secret $password + * @return void + * @throws peer.ConnectException + * @throws peer.ldap.LDAPException + */ + public function connect($user, $password) { + if (false === ($this->handle= ldap_connect($this->uri))) { + throw new ConnectException('Cannot connect to '.$this->uri); + } + + foreach ($this->params as $option => $value) { + $set= self::$options[$option]; + if (!$set($this->handle, $value)) { + ldap_unbind($this->handle); + $this->handle= null; + throw new ConnectException('Cannot set option "'.$option.'"', ldap_errno($this->handle)); + } + } + + $result= ldap_bind($this->handle, $user, $password ? $password->reveal() : null); + if (false === $result) { + $error= ldap_errno($this->handle); + ldap_unbind($this->handle); + $this->handle= null; + if (LDAP_SERVER_DOWN === $error || -1 === $error) { + throw new ConnectException('Cannot connect to '.$uri); + } else { + throw new LDAPException('Cannot bind for "'.($dn ?: $this->url->getUser(null)).'"', $error); + } + } + } + + public function search($scope, $base, $filter, $attributes= [], $attrsOnly= 0, $sizeLimit= 0, $timeLimit= 0, $sort= [], $deref= LDAP_DEREF_NEVER) { + static $methods= [ + LDAPQuery::SCOPE_BASE => 'ldap_read', + LDAPQuery::SCOPE_ONELEVEL => 'ldap_list', + LDAPQuery::SCOPE_SUB => 'ldap_search' + ]; + + if (!isset($methods[$scope])) { + throw new IllegalArgumentException('Scope '.$filter->getScope().' not supported'); + } + + if (false === ($res= $methods[$scope]($this->handle, $base, $filter, $attributes, $attrsOnly, $sizeLimit, $timeLimit, $deref))) { + throw $this->error('Search failed'); + } + + if ($sort) { + $entries= new SortedLDAPEntries($this->handle, $res, $sort); + } else { + $entries= new LDAPEntries($this->handle, $res); + } + + return new LDAPSearchResult($entries); + } + + /** + * Closes the connection + * + * @return void + */ + public function close() { + if ($this->handle) { + ldap_unbind($this->handle); + $this->handle= null; + } + } + + /** Ensures stream is closed. */ + public function __destruct() { $this->close(); } +} \ No newline at end of file From a2c60caabbcdfd6b856d28c6cbfddcb756622d06 Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Fri, 17 Aug 2018 00:59:41 +0200 Subject: [PATCH 20/32] Implement substring filter, hardcoded at the moment --- .../php/peer/ldap/util/BerStream.class.php | 60 +++++++++++-------- src/main/php/peer/ldap/util/Entries.class.php | 49 +++++++++++++++ .../php/peer/ldap/util/LdapProtocol.class.php | 60 +++++++++++++------ 3 files changed, 125 insertions(+), 44 deletions(-) create mode 100755 src/main/php/peer/ldap/util/Entries.class.php diff --git a/src/main/php/peer/ldap/util/BerStream.class.php b/src/main/php/peer/ldap/util/BerStream.class.php index ddb1f6f0..f5659050 100755 --- a/src/main/php/peer/ldap/util/BerStream.class.php +++ b/src/main/php/peer/ldap/util/BerStream.class.php @@ -53,31 +53,29 @@ class BerStream { public function __construct(InputStream $in, OutputStream $out) { // Debug - /* - $in= newinstance('io.streams.InputStream', [$in], [ - 'backing' => null, - '__construct' => function($backing) { $this->backing= $backing; }, - 'read' => function($length= 8192) { - $chunk= $this->backing->read($length); - if (null !== $chunk) { - \util\cmd\Console::writeLine(BerStream::dump($chunk, '<<<')); - } - return $chunk; - }, - 'available' => function() { return $this->backing->available(); }, - 'close' => function() { $this->backing->close(); } - ]); - $out= newinstance('io.streams.OutputStream', [$out], [ - 'backing' => null, - '__construct' => function($backing) { $this->backing= $backing; }, - 'write' => function($chunk) { - \util\cmd\Console::writeLine(BerStream::dump($chunk, '>>>')); - return $this->backing->write($chunk); - }, - 'flush' => function() { return $this->backing->flush(); }, - 'close' => function() { $this->backing->close(); } - ]); - */ + // $in= newinstance('io.streams.InputStream', [$in], [ + // 'backing' => null, + // '__construct' => function($backing) { $this->backing= $backing; }, + // 'read' => function($length= 8192) { + // $chunk= $this->backing->read($length); + // if (null !== $chunk) { + // \util\cmd\Console::writeLine(BerStream::dump($chunk, '<<<')); + // } + // return $chunk; + // }, + // 'available' => function() { return $this->backing->available(); }, + // 'close' => function() { $this->backing->close(); } + // ]); + // $out= newinstance('io.streams.OutputStream', [$out], [ + // 'backing' => null, + // '__construct' => function($backing) { $this->backing= $backing; }, + // 'write' => function($chunk) { + // \util\cmd\Console::writeLine(BerStream::dump($chunk, '>>>')); + // return $this->backing->write($chunk); + // }, + // 'flush' => function() { return $this->backing->flush(); }, + // 'close' => function() { $this->backing->close(); } + // ]); $this->in= $in instanceof BufferedInputStream ? $in : new BufferedInputStream($in, 8192); $this->out= $out; @@ -236,6 +234,18 @@ public function writeEnumeration($e, $tag= self::ENUMERATION) { $this->writeInt($e, $tag); } + /** + * Write enumeration to current sequence + * + * @param string $buffer + * @param int $tag + */ + public function writeBuffer($buffer, $tag) { + $this->writeByte($tag); + $this->writeLength(strlen($buffer)); + $this->write($buffer); + } + /** * Ends current sequences */ diff --git a/src/main/php/peer/ldap/util/Entries.class.php b/src/main/php/peer/ldap/util/Entries.class.php new file mode 100755 index 00000000..87472ddc --- /dev/null +++ b/src/main/php/peer/ldap/util/Entries.class.php @@ -0,0 +1,49 @@ +<?php namespace peer\ldap\util; + +use peer\ldap\LDAPEntry; + +class Entries { + private $list, $offset= 0; + + public function __construct($list) { + $this->list= $list; + } + + /** @return int */ + public function size() { return sizeof($this->list); } + + /** + * Gets first entry + * + * @return peer.ldap.LDAPEntry or NULL if nothing was found + * @throws peer.ldap.LDAPException in case of a read error + */ + public function first() { + if (empty($this->list)) return null; // Nothing found + + $this->offset= 0; + return LDAPEntry::create($this->list[0]['dn'], $this->list[0]['attr']); + } + + /** + * Gets next entry + * + * @return peer.ldap.LDAPEntry or NULL if nothing was found + * @throws peer.ldap.LDAPException in case of a read error + */ + public function next() { + if (++$this->offset >= sizeof($this->list) - 1) return null; + + $entry= LDAPEntry::create($this->list[$this->offset]['dn'], $this->list[$this->offset]['attr']); + return $entry; + } + + /** + * Close resultset and free result memory + * + * @return bool success + */ + public function close() { + $this->offset= 0; + } +} \ No newline at end of file diff --git a/src/main/php/peer/ldap/util/LdapProtocol.class.php b/src/main/php/peer/ldap/util/LdapProtocol.class.php index 21295073..d61842f5 100755 --- a/src/main/php/peer/ldap/util/LdapProtocol.class.php +++ b/src/main/php/peer/ldap/util/LdapProtocol.class.php @@ -1,6 +1,9 @@ <?php namespace peer\ldap\util; +use peer\SSLSocket; +use peer\Socket; use peer\ldap\LDAPException; +use peer\ldap\LDAPSearchResult; class LdapProtocol { const REQ_BIND = 0x60; @@ -42,18 +45,23 @@ class LdapProtocol { protected $messageId= 0; - /** - * Creates a new protocol instance communicating on the given socket - * - * @param peer.Socket $sock - */ - public function __construct(\peer\Socket $sock) { - $this->stream= new BerStream( - $sock->getInputStream(), - $sock->getOutputStream() - ); + public function __construct($scheme, $host, $port, $params) { + if ('ldaps' === $scheme) { + $this->sock= new SSLSocket($host, $port); + } else { + $this->sock= new Socket($host, $port); + } } + /** @return string */ + public function connection() { return $this->sock->toString(); } + + /** @return int */ + public function id() { return (int)$this->sock->getHandle(); } + + /** @return bool */ + public function connected() { return $this->sock->isConnected(); } + /** * Calculates and returns next message id, starting with 1. * @@ -118,17 +126,20 @@ public function send($message) { * Bind * * @param string $user - * @param string $password + * @param util.Secret $password * @return void * @throws peer.ldap.LDAPException */ - public function bind($user, $password) { + public function connect($user, $password) { + $this->sock->connect(); + $this->stream= new BerStream($this->sock->in(), $this->sock->out()); + $this->send([ 'req' => self::REQ_BIND, 'write' => function($stream) use($user, $password) { $stream->writeInt($version= 3); $stream->writeString($user); - $stream->writeString($password, BerStream::CONTEXT); + $stream->writeString($password->reveal(), BerStream::CONTEXT); }, 'res' => self::RES_BIND, 'read' => [self::RES_BIND => function($stream) { @@ -149,10 +160,10 @@ public function bind($user, $password) { * @param string $base * @return var */ - public function search($base) { - return $this->send([ + public function search($scope, $base, $filter, $attributes= [], $attrsOnly= 0, $sizeLimit= 0, $timeLimit= 0, $sort= [], $deref= LDAP_DEREF_NEVER) { + $r= $this->send([ 'req' => self::REQ_SEARCH, - 'write' => function($stream) use($base) { + 'write' => function($stream) use($base, $filter, $attributes) { $stream->writeString($base); $stream->writeEnumeration(self::SCOPE_ONE_LEVEL); $stream->writeEnumeration(self::NEVER_DEREF_ALIASES); @@ -160,11 +171,21 @@ public function search($base) { $stream->writeInt(0); $stream->writeBoolean(false); - $stream->startSequence(0x87); - $stream->write('objectClass'); + // substring filter {{{ + $stream->startSequence(0xa4); + + $stream->writeString('cn'); + $stream->startSequence(); + $stream->writeString('Friebe', 0x80); + $stream->endSequence(); + $stream->endSequence(); + // }}} $stream->startSequence(); + foreach ($attributes as $attribute) { + $stream->writeString($attribute); + } $stream->endSequence(); }, 'res' => [self::RES_SEARCH_ENTRY, self::RES_SEARCH], @@ -187,7 +208,7 @@ public function search($base) { $stream->finishSequence(); } while ($stream->remaining()); $stream->finishSequence(); - return ['name' => $name, 'attr' => $attributes]; + return ['dn' => $name, 'attr' => $attributes]; }, self::RES_SEARCH => function($stream) { $status= $stream->readEnumeration(); @@ -198,6 +219,7 @@ public function search($base) { } ] ]); + return new LDAPSearchResult(new Entries($r)); } /** From ec35815c5e422b73a74618a847a16afc28acd7b6 Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Sun, 9 Sep 2018 17:08:29 +0200 Subject: [PATCH 21/32] Add parser for LDAP filters See http://www.ldapexplorer.com/en/manual/109010000-ldap-filter-syntax.htm --- .../php/peer/ldap/filter/AndFilter.class.php | 9 ++ .../ldap/filter/ApproximateFilter.class.php | 10 ++ .../peer/ldap/filter/EqualityFilter.class.php | 10 ++ .../ldap/filter/ExtensibleFilter.class.php | 11 ++ .../php/peer/ldap/filter/Filter.class.php | 5 + .../php/peer/ldap/filter/Filters.class.php | 85 ++++++++++ .../filter/GreaterThanEqualsFilter.class.php | 10 ++ .../filter/LessThanEqualsFilter.class.php | 10 ++ .../php/peer/ldap/filter/NotFilter.class.php | 9 ++ .../php/peer/ldap/filter/OrFilter.class.php | 9 ++ .../peer/ldap/filter/PresenceFilter.class.php | 9 ++ .../ldap/filter/SubstringFilter.class.php | 12 ++ .../unittest/filter/FiltersTest.class.php | 151 ++++++++++++++++++ 13 files changed, 340 insertions(+) create mode 100755 src/main/php/peer/ldap/filter/AndFilter.class.php create mode 100755 src/main/php/peer/ldap/filter/ApproximateFilter.class.php create mode 100755 src/main/php/peer/ldap/filter/EqualityFilter.class.php create mode 100755 src/main/php/peer/ldap/filter/ExtensibleFilter.class.php create mode 100755 src/main/php/peer/ldap/filter/Filter.class.php create mode 100755 src/main/php/peer/ldap/filter/Filters.class.php create mode 100755 src/main/php/peer/ldap/filter/GreaterThanEqualsFilter.class.php create mode 100755 src/main/php/peer/ldap/filter/LessThanEqualsFilter.class.php create mode 100755 src/main/php/peer/ldap/filter/NotFilter.class.php create mode 100755 src/main/php/peer/ldap/filter/OrFilter.class.php create mode 100755 src/main/php/peer/ldap/filter/PresenceFilter.class.php create mode 100755 src/main/php/peer/ldap/filter/SubstringFilter.class.php create mode 100755 src/test/php/peer/ldap/unittest/filter/FiltersTest.class.php diff --git a/src/main/php/peer/ldap/filter/AndFilter.class.php b/src/main/php/peer/ldap/filter/AndFilter.class.php new file mode 100755 index 00000000..6791cfe1 --- /dev/null +++ b/src/main/php/peer/ldap/filter/AndFilter.class.php @@ -0,0 +1,9 @@ +<?php namespace peer\ldap\filter; + +class AndFilter implements Filter { + public $filters; + + public function __construct(... $filters) { + $this->filters= $filters; + } +} \ No newline at end of file diff --git a/src/main/php/peer/ldap/filter/ApproximateFilter.class.php b/src/main/php/peer/ldap/filter/ApproximateFilter.class.php new file mode 100755 index 00000000..0576e27d --- /dev/null +++ b/src/main/php/peer/ldap/filter/ApproximateFilter.class.php @@ -0,0 +1,10 @@ +<?php namespace peer\ldap\filter; + +class ApproximateFilter implements Filter { + public $attribute, $value; + + public function __construct($attribute, $value) { + $this->attribute= $attribute; + $this->value= $value; + } +} \ No newline at end of file diff --git a/src/main/php/peer/ldap/filter/EqualityFilter.class.php b/src/main/php/peer/ldap/filter/EqualityFilter.class.php new file mode 100755 index 00000000..203e1431 --- /dev/null +++ b/src/main/php/peer/ldap/filter/EqualityFilter.class.php @@ -0,0 +1,10 @@ +<?php namespace peer\ldap\filter; + +class EqualityFilter implements Filter { + public $attribute, $value; + + public function __construct($attribute, $value) { + $this->attribute= $attribute; + $this->value= $value; + } +} \ No newline at end of file diff --git a/src/main/php/peer/ldap/filter/ExtensibleFilter.class.php b/src/main/php/peer/ldap/filter/ExtensibleFilter.class.php new file mode 100755 index 00000000..19a3c624 --- /dev/null +++ b/src/main/php/peer/ldap/filter/ExtensibleFilter.class.php @@ -0,0 +1,11 @@ +<?php namespace peer\ldap\filter; + +class ExtensibleFilter implements Filter { + public $attribute, $rule, $value; + + public function __construct($attribute, $rule, $value) { + $this->attribute= $attribute; + $this->rule= $rule; + $this->value= $value; + } +} \ No newline at end of file diff --git a/src/main/php/peer/ldap/filter/Filter.class.php b/src/main/php/peer/ldap/filter/Filter.class.php new file mode 100755 index 00000000..feeaf21d --- /dev/null +++ b/src/main/php/peer/ldap/filter/Filter.class.php @@ -0,0 +1,5 @@ +<?php namespace peer\ldap\filter; + +interface Filter { + +} \ No newline at end of file diff --git a/src/main/php/peer/ldap/filter/Filters.class.php b/src/main/php/peer/ldap/filter/Filters.class.php new file mode 100755 index 00000000..6fd22915 --- /dev/null +++ b/src/main/php/peer/ldap/filter/Filters.class.php @@ -0,0 +1,85 @@ +<?php namespace peer\ldap\filter; + +/** + * Parses LDAP filters + * + * @test xp://peer.ldap.unittest.filter.FiltersTest + */ +class Filters { + + /** + * Returns all braced expressions belonging together + * + * @param string $input + * @return iterable + */ + private function all($input) { + $o= $b= 0; + $l= strlen($input); + + while ($o < $l) { + + // (&(objectClass=person)(cn~=Test))(cn=Test) + // ^ ^ + $s= $o; + do { + $o+= strcspn($input, '()', $o); + if ('(' === $input{$o}) $b++; else if (')' === $input{$o}) $b--; + } while ($o++ < $l && $b > 0); + + yield $this->parse(substr($input, $s, $o)); + } + } + + /** + * Parses a string + * + * @param string $input + * @param peer.ldap.filter.Filter + */ + public function parse($input) { + if ('(' === $input{0}) { + return $this->parse(substr($input, 1, -1)); + } else if ('&' === $input{0}) { + return new AndFilter(...$this->all(substr($input, 1))); + } else if ('|' === $input{0}) { + return new OrFilter(...$this->all(substr($input, 1))); + } else if ('!' === $input{0}) { + return new NotFilter($this->parse(substr($input, 1))); + } + + if (!preg_match('/([a-zA-Z0-9:;_.-]+[a-zA-Z0-9;_.-]+)([~><:]?=)(.+)/', $input, $matches)) { + throw new FormatException('Invalid filter `'.$input.'`'); + } + + switch ($matches[2]) { + case '=': + if ('*' === $matches[3]) return new PresenceFilter($matches[1]); + + $s= preg_split('/(?<!\\\)\*/', $matches[3]); + if (1 === sizeof($s)) { + return new EqualityFilter($matches[1], $matches[3]); + } else { + $initial= array_shift($s); + $final= array_pop($s); + return new SubstringFilter($matches[1], '' === $initial ? null : $initial, $s, '' === $final ? null : $final); + } + + case '~=': + return new ApproximateFilter($matches[1], $matches[3]); + + case '>=': + return new GreaterThanEqualsFilter($matches[1], $matches[3]); + + case '<=': + return new LessThanEqualsFilter($matches[1], $matches[3]); + + case ':=': + list($attribute, $rule)= explode(':', $matches[1], 2); + return new ExtensibleFilter($attribute, $rule, $matches[3]); + + default: + throw new FormatException('Invalid filter `'.$input.'`: Unrecognized operator `'.$matches[2].'`'); + } + } +} \ No newline at end of file diff --git a/src/main/php/peer/ldap/filter/GreaterThanEqualsFilter.class.php b/src/main/php/peer/ldap/filter/GreaterThanEqualsFilter.class.php new file mode 100755 index 00000000..e3af02d3 --- /dev/null +++ b/src/main/php/peer/ldap/filter/GreaterThanEqualsFilter.class.php @@ -0,0 +1,10 @@ +<?php namespace peer\ldap\filter; + +class GreaterThanEqualsFilter implements Filter { + public $attribute, $value; + + public function __construct($attribute, $value) { + $this->attribute= $attribute; + $this->value= $value; + } +} \ No newline at end of file diff --git a/src/main/php/peer/ldap/filter/LessThanEqualsFilter.class.php b/src/main/php/peer/ldap/filter/LessThanEqualsFilter.class.php new file mode 100755 index 00000000..f35eb672 --- /dev/null +++ b/src/main/php/peer/ldap/filter/LessThanEqualsFilter.class.php @@ -0,0 +1,10 @@ +<?php namespace peer\ldap\filter; + +class LessThanEqualsFilter implements Filter { + public $attribute, $value; + + public function __construct($attribute, $value) { + $this->attribute= $attribute; + $this->value= $value; + } +} \ No newline at end of file diff --git a/src/main/php/peer/ldap/filter/NotFilter.class.php b/src/main/php/peer/ldap/filter/NotFilter.class.php new file mode 100755 index 00000000..43cd8e84 --- /dev/null +++ b/src/main/php/peer/ldap/filter/NotFilter.class.php @@ -0,0 +1,9 @@ +<?php namespace peer\ldap\filter; + +class NotFilter implements Filter { + public $filter; + + public function __construct($filter) { + $this->filter= $filter; + } +} \ No newline at end of file diff --git a/src/main/php/peer/ldap/filter/OrFilter.class.php b/src/main/php/peer/ldap/filter/OrFilter.class.php new file mode 100755 index 00000000..4f6cfc27 --- /dev/null +++ b/src/main/php/peer/ldap/filter/OrFilter.class.php @@ -0,0 +1,9 @@ +<?php namespace peer\ldap\filter; + +class OrFilter implements Filter { + public $filters; + + public function __construct(... $filters) { + $this->filters= $filters; + } +} \ No newline at end of file diff --git a/src/main/php/peer/ldap/filter/PresenceFilter.class.php b/src/main/php/peer/ldap/filter/PresenceFilter.class.php new file mode 100755 index 00000000..989c1e55 --- /dev/null +++ b/src/main/php/peer/ldap/filter/PresenceFilter.class.php @@ -0,0 +1,9 @@ +<?php namespace peer\ldap\filter; + +class PresenceFilter implements Filter { + public $attribute; + + public function __construct($attribute) { + $this->attribute= $attribute; + } +} \ No newline at end of file diff --git a/src/main/php/peer/ldap/filter/SubstringFilter.class.php b/src/main/php/peer/ldap/filter/SubstringFilter.class.php new file mode 100755 index 00000000..cdbe5d7d --- /dev/null +++ b/src/main/php/peer/ldap/filter/SubstringFilter.class.php @@ -0,0 +1,12 @@ +<?php namespace peer\ldap\filter; + +class SubstringFilter implements Filter { + public $attribute, $initial, $any, $final; + + public function __construct($attribute, $initial, $any, $final) { + $this->attribute= $attribute; + $this->initial= $initial; + $this->any= $any; + $this->final= $final; + } +} \ No newline at end of file diff --git a/src/test/php/peer/ldap/unittest/filter/FiltersTest.class.php b/src/test/php/peer/ldap/unittest/filter/FiltersTest.class.php new file mode 100755 index 00000000..f7b0e66b --- /dev/null +++ b/src/test/php/peer/ldap/unittest/filter/FiltersTest.class.php @@ -0,0 +1,151 @@ +<?php namespace peer\ldap\unittest\filter; + +use peer\ldap\filter\AndFilter; +use peer\ldap\filter\ApproximateFilter; +use peer\ldap\filter\EqualityFilter; +use peer\ldap\filter\ExtensibleFilter; +use peer\ldap\filter\Filters; +use peer\ldap\filter\GreaterThanEqualsFilter; +use peer\ldap\filter\LessThanEqualsFilter; +use peer\ldap\filter\NotFilter; +use peer\ldap\filter\OrFilter; +use peer\ldap\filter\PresenceFilter; +use peer\ldap\filter\SubstringFilter; +use unittest\TestCase; + +class FiltersTest extends TestCase { + + #[@test] + public function can_create() { + new Filters(); + } + + #[@test] + public function presence() { + $this->assertEquals( + new PresenceFilter('objectClass'), + (new Filters())->parse('objectClass=*') + ); + } + + #[@test, @values([ + # 'person', + # '\*person', + # 'person\*', + #])] + public function equality($value) { + $this->assertEquals( + new EqualityFilter('objectClass', $value), + (new Filters())->parse('objectClass='.$value) + ); + } + + #[@test] + public function substring_initial() { + $this->assertEquals( + new SubstringFilter('objectClass', 'person', [], null), + (new Filters())->parse('objectClass=person*') + ); + } + + #[@test] + public function substring_final() { + $this->assertEquals( + new SubstringFilter('objectClass', null, [], 'person'), + (new Filters())->parse('objectClass=*person') + ); + } + + #[@test] + public function substring_initial_and_final() { + $this->assertEquals( + new SubstringFilter('objectClass', 'a', [], 'b'), + (new Filters())->parse('objectClass=a*b') + ); + } + + #[@test, @values([ + # ['a*b*c*d', ['a', ['b', 'c'], 'd']], + # ['*b*c*d', [null, ['b', 'c'], 'd']], + # ['a*b*c*', ['a', ['b', 'c'], null]], + #])] + public function substring_any($input, $expected) { + $this->assertEquals( + new SubstringFilter('objectClass', ...$expected), + (new Filters())->parse('objectClass='.$input) + ); + } + + #[@test] + public function approximate() { + $this->assertEquals( + new ApproximateFilter('cn', 'Test'), + (new Filters())->parse('cn~=Test') + ); + } + + #[@test] + public function greater_than() { + $this->assertEquals( + new GreaterThanEqualsFilter('storageQuota', '100'), + (new Filters())->parse('storageQuota>=100') + ); + } + + #[@test] + public function less_than() { + $this->assertEquals( + new LessThanEqualsFilter('storageQuota', '100'), + (new Filters())->parse('storageQuota<=100') + ); + } + + #[@test] + public function extensible() { + $this->assertEquals( + new ExtensibleFilter('userAccountControl', '1.2.840.113556.1.4.804', '65568'), + (new Filters())->parse('userAccountControl:1.2.840.113556.1.4.804:=65568') + ); + } + + #[@test] + public function braces() { + $this->assertEquals( + new EqualityFilter('cn', 'Test'), + (new Filters())->parse('(cn=Test)') + ); + } + + #[@test] + public function not() { + $this->assertEquals( + new NotFilter(new EqualityFilter('cn', 'Test')), + (new Filters())->parse('!(cn=Test)') + ); + } + + #[@test] + public function and() { + $this->assertEquals( + new AndFilter( + new EqualityFilter('objectClass', 'person'), + new ApproximateFilter('cn', 'Test') + ), + (new Filters())->parse('&(objectClass=person)(cn~=Test)') + ); + } + + #[@test] + public function and_and_or() { + $this->assertEquals( + new OrFilter( + new AndFilter( + new EqualityFilter('objectClass', 'person'), + new ApproximateFilter('cn', 'Test') + ), + new EqualityFilter('cn', 'Test') + ), + (new Filters())->parse('|(&(objectClass=person)(cn~=Test))(cn=Test)') + ); + } +} \ No newline at end of file From bbf6dbc62b83a59fb341804e38c49145d12eceb2 Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Sun, 9 Sep 2018 17:17:21 +0200 Subject: [PATCH 22/32] Add tests for invalid LDAP filter syntax --- .../php/peer/ldap/filter/Filters.class.php | 58 +++++++++---------- .../unittest/filter/FiltersTest.class.php | 13 +++++ 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/main/php/peer/ldap/filter/Filters.class.php b/src/main/php/peer/ldap/filter/Filters.class.php index 6fd22915..7789d581 100755 --- a/src/main/php/peer/ldap/filter/Filters.class.php +++ b/src/main/php/peer/ldap/filter/Filters.class.php @@ -1,5 +1,7 @@ <?php namespace peer\ldap\filter; +use lang\FormatException; + /** * Parses LDAP filters * @@ -38,7 +40,7 @@ private function all($input) { * @param peer.ldap.filter.Filter */ public function parse($input) { - if ('(' === $input{0}) { + if ('(' === $input{0} && ')' === $input{strlen($input) - 1}) { return $this->parse(substr($input, 1, -1)); } else if ('&' === $input{0}) { return new AndFilter(...$this->all(substr($input, 1))); @@ -46,40 +48,38 @@ public function parse($input) { return new OrFilter(...$this->all(substr($input, 1))); } else if ('!' === $input{0}) { return new NotFilter($this->parse(substr($input, 1))); - } - - if (!preg_match('/([a-zA-Z0-9:;_.-]+[a-zA-Z0-9;_.-]+)([~><:]?=)(.+)/', $input, $matches)) { - throw new FormatException('Invalid filter `'.$input.'`'); - } - - switch ($matches[2]) { - case '=': - if ('*' === $matches[3]) return new PresenceFilter($matches[1]); + } else if (preg_match('/^([a-zA-Z0-9:;_.-]+[a-zA-Z0-9;_.-]+)([~><:]?=)(.+)$/', $input, $matches)) { + switch ($matches[2]) { + case '=': + if ('*' === $matches[3]) return new PresenceFilter($matches[1]); - $s= preg_split('/(?<!\\\)\*/', $matches[3]); - if (1 === sizeof($s)) { - return new EqualityFilter($matches[1], $matches[3]); - } else { - $initial= array_shift($s); - $final= array_pop($s); - return new SubstringFilter($matches[1], '' === $initial ? null : $initial, $s, '' === $final ? null : $final); - } + $s= preg_split('/(?<!\\\)\*/', $matches[3]); + if (1 === sizeof($s)) { + return new EqualityFilter($matches[1], $matches[3]); + } else { + $initial= array_shift($s); + $final= array_pop($s); + return new SubstringFilter($matches[1], '' === $initial ? null : $initial, $s, '' === $final ? null : $final); + } - case '~=': - return new ApproximateFilter($matches[1], $matches[3]); + case '~=': + return new ApproximateFilter($matches[1], $matches[3]); - case '>=': - return new GreaterThanEqualsFilter($matches[1], $matches[3]); + case '>=': + return new GreaterThanEqualsFilter($matches[1], $matches[3]); - case '<=': - return new LessThanEqualsFilter($matches[1], $matches[3]); + case '<=': + return new LessThanEqualsFilter($matches[1], $matches[3]); - case ':=': - list($attribute, $rule)= explode(':', $matches[1], 2); - return new ExtensibleFilter($attribute, $rule, $matches[3]); + case ':=': + list($attribute, $rule)= explode(':', $matches[1], 2); + return new ExtensibleFilter($attribute, $rule, $matches[3]); - default: - throw new FormatException('Invalid filter `'.$input.'`: Unrecognized operator `'.$matches[2].'`'); + default: + throw new FormatException('Invalid filter `'.$input.'`: Unrecognized operator `'.$matches[2].'`'); + } } + + throw new FormatException('Invalid filter `'.$input.'`: Expected `(`, `&`, `|`, `!` or criteria'); } } \ No newline at end of file diff --git a/src/test/php/peer/ldap/unittest/filter/FiltersTest.class.php b/src/test/php/peer/ldap/unittest/filter/FiltersTest.class.php index f7b0e66b..996342f3 100755 --- a/src/test/php/peer/ldap/unittest/filter/FiltersTest.class.php +++ b/src/test/php/peer/ldap/unittest/filter/FiltersTest.class.php @@ -1,5 +1,6 @@ <?php namespace peer\ldap\unittest\filter; +use lang\FormatException; use peer\ldap\filter\AndFilter; use peer\ldap\filter\ApproximateFilter; use peer\ldap\filter\EqualityFilter; @@ -148,4 +149,16 @@ public function and_and_or() { (new Filters())->parse('|(&(objectClass=person)(cn~=Test))(cn=Test)') ); } + + #[@test, @expect(FormatException::class), @values([ + # 'cn=', + # '!cn=', + # '^cn=value', + # 'cn^=value', + # '(cn=value', + # 'GET / HTTP/1.1', + #])] + public function invalid($syntax) { + (new Filters())->parse($syntax); + } } \ No newline at end of file From 98419f482ea7e5ec16da40ddfe2c35bb3dd60de6 Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Sun, 9 Sep 2018 17:19:54 +0200 Subject: [PATCH 23/32] Fix PHP 5.6 compatibility syntax error, unexpected "and" (T_LOGICAL_AND), expecting identifier (T_STRING) --- .../php/peer/ldap/unittest/filter/FiltersTest.class.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/php/peer/ldap/unittest/filter/FiltersTest.class.php b/src/test/php/peer/ldap/unittest/filter/FiltersTest.class.php index 996342f3..cabee915 100755 --- a/src/test/php/peer/ldap/unittest/filter/FiltersTest.class.php +++ b/src/test/php/peer/ldap/unittest/filter/FiltersTest.class.php @@ -118,7 +118,7 @@ public function braces() { } #[@test] - public function not() { + public function logical_not() { $this->assertEquals( new NotFilter(new EqualityFilter('cn', 'Test')), (new Filters())->parse('!(cn=Test)') @@ -126,7 +126,7 @@ public function not() { } #[@test] - public function and() { + public function logical_and() { $this->assertEquals( new AndFilter( new EqualityFilter('objectClass', 'person'), @@ -137,7 +137,7 @@ public function and() { } #[@test] - public function and_and_or() { + public function logical_and_and_or() { $this->assertEquals( new OrFilter( new AndFilter( From 4bfd8aaac999e5c7b9cb1ae061a3bf99dc9802ba Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Sun, 9 Sep 2018 20:08:47 +0200 Subject: [PATCH 24/32] Rewrite all() to return an array Should fix HHVM incompatibility --- src/main/php/peer/ldap/filter/Filters.class.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/php/peer/ldap/filter/Filters.class.php b/src/main/php/peer/ldap/filter/Filters.class.php index 7789d581..d0a3f42b 100755 --- a/src/main/php/peer/ldap/filter/Filters.class.php +++ b/src/main/php/peer/ldap/filter/Filters.class.php @@ -13,12 +13,12 @@ class Filters { * Returns all braced expressions belonging together * * @param string $input - * @return iterable + * @return peer.ldap.filter.Filter[] */ private function all($input) { $o= $b= 0; $l= strlen($input); - + $r= []; while ($o < $l) { // (&(objectClass=person)(cn~=Test))(cn=Test) @@ -29,8 +29,9 @@ private function all($input) { if ('(' === $input{$o}) $b++; else if (')' === $input{$o}) $b--; } while ($o++ < $l && $b > 0); - yield $this->parse(substr($input, $s, $o)); + $r[]= $this->parse(substr($input, $s, $o)); } + return $r; } /** From c8945e5afac9a636c689d837291fac5f3a360131 Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Sun, 9 Sep 2018 21:23:31 +0200 Subject: [PATCH 25/32] Add public "kind" member --- src/main/php/peer/ldap/filter/AndFilter.class.php | 2 ++ src/main/php/peer/ldap/filter/ApproximateFilter.class.php | 2 ++ src/main/php/peer/ldap/filter/EqualityFilter.class.php | 2 ++ src/main/php/peer/ldap/filter/ExtensibleFilter.class.php | 8 +++++--- .../peer/ldap/filter/GreaterThanEqualsFilter.class.php | 2 ++ .../php/peer/ldap/filter/LessThanEqualsFilter.class.php | 2 ++ src/main/php/peer/ldap/filter/NotFilter.class.php | 2 ++ src/main/php/peer/ldap/filter/OrFilter.class.php | 2 ++ src/main/php/peer/ldap/filter/PresenceFilter.class.php | 2 ++ src/main/php/peer/ldap/filter/SubstringFilter.class.php | 2 ++ 10 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/main/php/peer/ldap/filter/AndFilter.class.php b/src/main/php/peer/ldap/filter/AndFilter.class.php index 6791cfe1..9d7bf934 100755 --- a/src/main/php/peer/ldap/filter/AndFilter.class.php +++ b/src/main/php/peer/ldap/filter/AndFilter.class.php @@ -1,6 +1,8 @@ <?php namespace peer\ldap\filter; class AndFilter implements Filter { + public $kind= 'and'; + public $filters; public function __construct(... $filters) { diff --git a/src/main/php/peer/ldap/filter/ApproximateFilter.class.php b/src/main/php/peer/ldap/filter/ApproximateFilter.class.php index 0576e27d..0048f7d7 100755 --- a/src/main/php/peer/ldap/filter/ApproximateFilter.class.php +++ b/src/main/php/peer/ldap/filter/ApproximateFilter.class.php @@ -1,6 +1,8 @@ <?php namespace peer\ldap\filter; class ApproximateFilter implements Filter { + public $kind= 'approximate'; + public $attribute, $value; public function __construct($attribute, $value) { diff --git a/src/main/php/peer/ldap/filter/EqualityFilter.class.php b/src/main/php/peer/ldap/filter/EqualityFilter.class.php index 203e1431..de9322ab 100755 --- a/src/main/php/peer/ldap/filter/EqualityFilter.class.php +++ b/src/main/php/peer/ldap/filter/EqualityFilter.class.php @@ -1,6 +1,8 @@ <?php namespace peer\ldap\filter; class EqualityFilter implements Filter { + public $kind= 'equality'; + public $attribute, $value; public function __construct($attribute, $value) { diff --git a/src/main/php/peer/ldap/filter/ExtensibleFilter.class.php b/src/main/php/peer/ldap/filter/ExtensibleFilter.class.php index 19a3c624..4b648c69 100755 --- a/src/main/php/peer/ldap/filter/ExtensibleFilter.class.php +++ b/src/main/php/peer/ldap/filter/ExtensibleFilter.class.php @@ -1,10 +1,12 @@ <?php namespace peer\ldap\filter; class ExtensibleFilter implements Filter { - public $attribute, $rule, $value; + public $kind= 'extensible'; - public function __construct($attribute, $rule, $value) { - $this->attribute= $attribute; + public $type, $rule, $value, $attributes; + + public function __construct($type, $rule, $value, $attributes= false) { + $this->type= $type; $this->rule= $rule; $this->value= $value; } diff --git a/src/main/php/peer/ldap/filter/GreaterThanEqualsFilter.class.php b/src/main/php/peer/ldap/filter/GreaterThanEqualsFilter.class.php index e3af02d3..eead0519 100755 --- a/src/main/php/peer/ldap/filter/GreaterThanEqualsFilter.class.php +++ b/src/main/php/peer/ldap/filter/GreaterThanEqualsFilter.class.php @@ -1,6 +1,8 @@ <?php namespace peer\ldap\filter; class GreaterThanEqualsFilter implements Filter { + public $kind= 'greaterthanequals'; + public $attribute, $value; public function __construct($attribute, $value) { diff --git a/src/main/php/peer/ldap/filter/LessThanEqualsFilter.class.php b/src/main/php/peer/ldap/filter/LessThanEqualsFilter.class.php index f35eb672..45b0629a 100755 --- a/src/main/php/peer/ldap/filter/LessThanEqualsFilter.class.php +++ b/src/main/php/peer/ldap/filter/LessThanEqualsFilter.class.php @@ -1,6 +1,8 @@ <?php namespace peer\ldap\filter; class LessThanEqualsFilter implements Filter { + public $kind= 'lessthanequals'; + public $attribute, $value; public function __construct($attribute, $value) { diff --git a/src/main/php/peer/ldap/filter/NotFilter.class.php b/src/main/php/peer/ldap/filter/NotFilter.class.php index 43cd8e84..a81638e8 100755 --- a/src/main/php/peer/ldap/filter/NotFilter.class.php +++ b/src/main/php/peer/ldap/filter/NotFilter.class.php @@ -1,6 +1,8 @@ <?php namespace peer\ldap\filter; class NotFilter implements Filter { + public $kind= 'not'; + public $filter; public function __construct($filter) { diff --git a/src/main/php/peer/ldap/filter/OrFilter.class.php b/src/main/php/peer/ldap/filter/OrFilter.class.php index 4f6cfc27..2b072132 100755 --- a/src/main/php/peer/ldap/filter/OrFilter.class.php +++ b/src/main/php/peer/ldap/filter/OrFilter.class.php @@ -1,6 +1,8 @@ <?php namespace peer\ldap\filter; class OrFilter implements Filter { + public $kind= 'or'; + public $filters; public function __construct(... $filters) { diff --git a/src/main/php/peer/ldap/filter/PresenceFilter.class.php b/src/main/php/peer/ldap/filter/PresenceFilter.class.php index 989c1e55..43130235 100755 --- a/src/main/php/peer/ldap/filter/PresenceFilter.class.php +++ b/src/main/php/peer/ldap/filter/PresenceFilter.class.php @@ -1,6 +1,8 @@ <?php namespace peer\ldap\filter; class PresenceFilter implements Filter { + public $kind= 'presence'; + public $attribute; public function __construct($attribute) { diff --git a/src/main/php/peer/ldap/filter/SubstringFilter.class.php b/src/main/php/peer/ldap/filter/SubstringFilter.class.php index cdbe5d7d..76d42ffe 100755 --- a/src/main/php/peer/ldap/filter/SubstringFilter.class.php +++ b/src/main/php/peer/ldap/filter/SubstringFilter.class.php @@ -1,6 +1,8 @@ <?php namespace peer\ldap\filter; class SubstringFilter implements Filter { + public $kind= 'substring'; + public $attribute, $initial, $any, $final; public function __construct($attribute, $initial, $any, $final) { From a0d6abb274e9857aeb0c180160d094fbad8a7ce7 Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Sun, 9 Sep 2018 21:23:50 +0200 Subject: [PATCH 26/32] Use filters API --- src/main/php/peer/ldap/util/LdapProtocol.class.php | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/main/php/peer/ldap/util/LdapProtocol.class.php b/src/main/php/peer/ldap/util/LdapProtocol.class.php index d61842f5..83496857 100755 --- a/src/main/php/peer/ldap/util/LdapProtocol.class.php +++ b/src/main/php/peer/ldap/util/LdapProtocol.class.php @@ -4,6 +4,7 @@ use peer\Socket; use peer\ldap\LDAPException; use peer\ldap\LDAPSearchResult; +use peer\ldap\filter\Filters; class LdapProtocol { const REQ_BIND = 0x60; @@ -51,6 +52,7 @@ public function __construct($scheme, $host, $port, $params) { } else { $this->sock= new Socket($host, $port); } + $this->filters= new Filters(); } /** @return string */ @@ -171,16 +173,7 @@ public function search($scope, $base, $filter, $attributes= [], $attrsOnly= 0, $ $stream->writeInt(0); $stream->writeBoolean(false); - // substring filter {{{ - $stream->startSequence(0xa4); - - $stream->writeString('cn'); - $stream->startSequence(); - $stream->writeString('Friebe', 0x80); - $stream->endSequence(); - - $stream->endSequence(); - // }}} + $stream->writeFilter($this->filters->parse($filter)); $stream->startSequence(); foreach ($attributes as $attribute) { From b599a3cae9763981d45a6b4f03f43465e91c208b Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Sun, 9 Sep 2018 21:24:01 +0200 Subject: [PATCH 27/32] Implement writing filters --- .../php/peer/ldap/util/BerStream.class.php | 113 +++++++++++++++++- 1 file changed, 112 insertions(+), 1 deletion(-) diff --git a/src/main/php/peer/ldap/util/BerStream.class.php b/src/main/php/peer/ldap/util/BerStream.class.php index f5659050..5dfa6528 100755 --- a/src/main/php/peer/ldap/util/BerStream.class.php +++ b/src/main/php/peer/ldap/util/BerStream.class.php @@ -246,8 +246,119 @@ public function writeBuffer($buffer, $tag) { $this->write($buffer); } + /** + * Writes a filter + * + * @param peer.ldap.filter.Filter + * @return void + */ + public function writeFilter($filter) { + $this->{'writeFilter'.$filter->kind}($filter); + } + + /** FILTER_AND = 0xa0 */ + private function writeFilterAnd($and) { + $this->startSequence(0xa0); + foreach ($and->filters as $filter) { + $this->{'writeFilter'.$filter->kind}($filter); + } + $this->endSequence(); + } + + /** FILTER_AND = 0xa1 */ + private function writeFilterOr($or) { + $this->startSequence(0xa1); + foreach ($or->filters as $filter) { + $this->{'writeFilter'.$filter->kind}($filter); + } + $this->endSequence(); + } + + /** FILTER_AND = 0xa2 */ + private function writeFilterNot($not) { + $this->startSequence(0xa2); + $this->{'writeFilter'.$not->filter->kind}($not->filter); + $this->endSequence(); + } + + /** FILTER_EQUALITY = 0xa3 */ + private function writeFilterEquality($eq) { + $this->startSequence(0xa4); + $this->writeString($eq->attribute); + $this->writeBuffer($eq->value, self::OCTETSTRING); + $this->endSequence(); + } + + /** FILTER_SUBSTRINGS = 0xa4 */ + private function writeFilterSubstring($substr) { + $this->startSequence(0xa4); + $this->writeString($substr->attribute); + + $this->startSequence(); + if (null !== $substr->initial) { + $this->writeString($substr->initial, 0x80); + } + foreach ($substr->any as $string) { + $this->writeString($substr->any, 0x81); + } + if (null !== $substr->final) { + $this->writeString($substr->final, 0x82); + } + $this->endSequence(); + + $this->endSequence(); + } + + /** FILTER_GE = 0xa5 */ + private function writeFilterGreaterThanEquals($ge) { + $this->startSequence(0xa5); + $this->writeString($ge->attribute); + $this->writeString($ge->value); + $this->endSequence(); + } + + /** FILTER_LE = 0xa6 */ + private function writeFilterLessThanEquals($le) { + $this->startSequence(0xa6); + $this->writeString($le->attribute); + $this->writeString($le->value); + $this->endSequence(); + } + + /** FILTER_PRESENT = 0x87 */ + private function writeFilterPresent($present) { + $this->startSequence(0x87); + for ($i= 0, $l= strlen($present->attribute); $i < $l; $i++) { + $this->writeByte($i); + } + $this->endSequence(); + } + + /** FILTER_APPROX = 0xa8 */ + private function writeFilterApproximate($approx) { + $this->startSequence(0xa8); + $this->writeString($approx->attribute); + $this->writeString($approx->value); + $this->endSequence(); + } + + /** FILTER_EXT = 0xa9 */ + private function writeFilterExtensible($approx) { + $this->startSequence(0xa09); + + $this->writeString($approx->rule, 0x81); + $this->writeString($approx->type, 0x82); + $this->writeString($approx->value, 0x83); + if ($approx->attributes) { + $this->writeBoolean($approx->type, 0x84); + } + $this->endSequence(); + } + /** * Ends current sequences + * + * @return void */ public function endSequence() { $length= $this->encodeLength(strlen($this->write[0]) - 1); @@ -267,7 +378,7 @@ public function flush() { } public function read($l) { - $t= debug_backtrace(); + // $t= debug_backtrace(); $chunk= $this->in->read($l); $this->read[0]-= strlen($chunk); // fprintf(STDOUT, "%s READ %d bytes from %s, remain %d\n", str_repeat(' ', sizeof($this->read)), $l, $t[1]['function'], $this->read[0]); From 3b183592a4985b8546ea90295408987068b27b50 Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Sun, 9 Sep 2018 22:59:17 +0200 Subject: [PATCH 28/32] Add peek() --- .../php/peer/ldap/util/BerStream.class.php | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/main/php/peer/ldap/util/BerStream.class.php b/src/main/php/peer/ldap/util/BerStream.class.php index 5dfa6528..c20f7429 100755 --- a/src/main/php/peer/ldap/util/BerStream.class.php +++ b/src/main/php/peer/ldap/util/BerStream.class.php @@ -41,6 +41,7 @@ class BerStream { protected $write= ['']; protected $read= [0]; + protected $buffer= null; protected $in, $out; @@ -283,7 +284,7 @@ private function writeFilterNot($not) { /** FILTER_EQUALITY = 0xa3 */ private function writeFilterEquality($eq) { - $this->startSequence(0xa4); + $this->startSequence(0xa3); $this->writeString($eq->attribute); $this->writeBuffer($eq->value, self::OCTETSTRING); $this->endSequence(); @@ -378,15 +379,29 @@ public function flush() { } public function read($l) { + $chunk= $this->buffer; + $this->buffer= null; + while (strlen($chunk) < $l) { + $bytes= $this->in->read($l - strlen($chunk)); + $this->read[0]-= strlen($bytes); + $chunk.= $bytes; + } + // $t= debug_backtrace(); - $chunk= $this->in->read($l); - $this->read[0]-= strlen($chunk); // fprintf(STDOUT, "%s READ %d bytes from %s, remain %d\n", str_repeat(' ', sizeof($this->read)), $l, $t[1]['function'], $this->read[0]); + return $chunk; } + public function peek() { + if (null === $this->buffer) { + $this->buffer= $this->in->read(1); + } + return unpack('Ctag', $this->buffer)['tag']; + } + public function readTag($expected) { - $head= unpack('Ctag', $this->in->read(1)); + $head= unpack('Ctag', $this->read(1)); $test= (array)$expected; if (!in_array($head['tag'], $test)) { throw new \lang\IllegalStateException(sprintf( @@ -445,10 +460,11 @@ public function readEnumeration() { /** * Reads a string * + * @param int $tag * @return string */ - public function readString() { - $this->readTag(self::OCTETSTRING); + public function readString($tag= self::OCTETSTRING) { + $this->readTag($tag); return $this->read($this->decodeLength()); } From c4a71671d562277bf6579600628d167dda0557f0 Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Sun, 9 Sep 2018 22:59:32 +0200 Subject: [PATCH 29/32] Implement RES_EXTENSION --- .../php/peer/ldap/util/LdapProtocol.class.php | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/php/peer/ldap/util/LdapProtocol.class.php b/src/main/php/peer/ldap/util/LdapProtocol.class.php index 83496857..e8c10269 100755 --- a/src/main/php/peer/ldap/util/LdapProtocol.class.php +++ b/src/main/php/peer/ldap/util/LdapProtocol.class.php @@ -41,7 +41,8 @@ class LdapProtocol { const STATUS_OK = 0; protected static $continue= [ - self::RES_SEARCH_ENTRY => true + self::RES_SEARCH_ENTRY => true, + self::RES_EXTENSION => true, ]; protected $messageId= 0; @@ -181,7 +182,7 @@ public function search($scope, $base, $filter, $attributes= [], $attrsOnly= 0, $ } $stream->endSequence(); }, - 'res' => [self::RES_SEARCH_ENTRY, self::RES_SEARCH], + 'res' => [self::RES_SEARCH_ENTRY, self::RES_SEARCH, self::RES_EXTENSION], 'read' => [ self::RES_SEARCH_ENTRY => function($stream) { $name= $stream->readString(); @@ -195,11 +196,11 @@ public function search($scope, $base, $filter, $attributes= [], $attrsOnly= 0, $ $attributes[$attr]= []; do { $attributes[$attr][]= $stream->readString(); - } while ($stream->remaining()); + } while ($stream->remaining() > 0); $stream->finishSequence(); $stream->finishSequence(); - } while ($stream->remaining()); + } while ($stream->remaining() > 0); $stream->finishSequence(); return ['dn' => $name, 'attr' => $attributes]; }, @@ -209,6 +210,16 @@ public function search($scope, $base, $filter, $attributes= [], $attrsOnly= 0, $ $error= $stream->readString(); $this->handleResponse($status, $error ?: 'Search failed'); + }, + self::RES_EXTENSION => function($stream) { + $status= $stream->readEnumeration(); + $matchedDN= $stream->readString(); + $error= $stream->readString(); + + $name= 0x8a === $stream->peek() ? $stream->readString(0x8a) : null; + // $value= 0x8b === $stream->peek() ? $stream->readString(0x8b) : null; + + $this->handleResponse($status, ($error ?: 'Search failed').($name ? ': '.$name : '')); } ] ]); From b080bde017b6db3d94dcf8810c3e57680694edbd Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Sun, 9 Sep 2018 23:01:54 +0200 Subject: [PATCH 30/32] Fix size --- src/main/php/peer/ldap/util/Entries.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/php/peer/ldap/util/Entries.class.php b/src/main/php/peer/ldap/util/Entries.class.php index 87472ddc..1f7af388 100755 --- a/src/main/php/peer/ldap/util/Entries.class.php +++ b/src/main/php/peer/ldap/util/Entries.class.php @@ -10,7 +10,7 @@ public function __construct($list) { } /** @return int */ - public function size() { return sizeof($this->list); } + public function size() { return sizeof($this->list) - 1; } /** * Gets first entry From 9416b66e8278d9b003ed1d19421ac3acef318e7f Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Mon, 10 Sep 2018 09:47:42 +0200 Subject: [PATCH 31/32] Extract wrapping in LDAPSearchResult to LDAPConnection class --- src/main/php/peer/ldap/LDAPConnection.class.php | 8 ++++---- src/main/php/peer/ldap/util/LdapLibrary.class.php | 2 +- src/main/php/peer/ldap/util/LdapProtocol.class.php | 4 +++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/php/peer/ldap/LDAPConnection.class.php b/src/main/php/peer/ldap/LDAPConnection.class.php index dec8b44c..887751d2 100755 --- a/src/main/php/peer/ldap/LDAPConnection.class.php +++ b/src/main/php/peer/ldap/LDAPConnection.class.php @@ -133,7 +133,7 @@ private function error($message) { * @see php://ldap_search */ public function search($base, $filter, $attributes= [], $attrsOnly= 0, $sizeLimit= 0, $timeLimit= 0, $deref= LDAP_DEREF_NEVER) { - return $this->proto->search( + return new LDAPSearchResult($this->proto->search( LDAPQuery::SCOPE_SUB, $base, $filter, @@ -143,7 +143,7 @@ public function search($base, $filter, $attributes= [], $attrsOnly= 0, $sizeLimi $timeLimit, null, $deref - ); + )); } /** @@ -153,7 +153,7 @@ public function search($base, $filter, $attributes= [], $attrsOnly= 0, $sizeLimi * @return peer.ldap.LDAPSearchResult search result object */ public function searchBy(LDAPQuery $filter) { - return $this->proto->search( + return new LDAPSearchResult($this->proto->search( $filter->getScope(), $filter->getBase(), $filter->getFilter(), @@ -163,7 +163,7 @@ public function searchBy(LDAPQuery $filter) { $filter->getTimelimit(), $filter->getSort(), $filter->getDeref() - ); + )); } /** diff --git a/src/main/php/peer/ldap/util/LdapLibrary.class.php b/src/main/php/peer/ldap/util/LdapLibrary.class.php index cca6e5b9..6f185413 100755 --- a/src/main/php/peer/ldap/util/LdapLibrary.class.php +++ b/src/main/php/peer/ldap/util/LdapLibrary.class.php @@ -133,7 +133,7 @@ public function search($scope, $base, $filter, $attributes= [], $attrsOnly= 0, $ $entries= new LDAPEntries($this->handle, $res); } - return new LDAPSearchResult($entries); + return $entries; } /** diff --git a/src/main/php/peer/ldap/util/LdapProtocol.class.php b/src/main/php/peer/ldap/util/LdapProtocol.class.php index e8c10269..23c13480 100755 --- a/src/main/php/peer/ldap/util/LdapProtocol.class.php +++ b/src/main/php/peer/ldap/util/LdapProtocol.class.php @@ -223,11 +223,13 @@ public function search($scope, $base, $filter, $attributes= [], $attrsOnly= 0, $ } ] ]); - return new LDAPSearchResult(new Entries($r)); + return new Entries($r); } /** * Closes the connection + * + * @return void */ public function close() { $this->stream->close(); From 6278ac6104c2bdce6361cb65d262dc3e54dd327d Mon Sep 17 00:00:00 2001 From: Timm Friebe <thekid@thekid.de> Date: Mon, 10 Sep 2018 11:06:28 +0200 Subject: [PATCH 32/32] QA: Apidocs, whitespace --- .../php/peer/ldap/util/LdapProtocol.class.php | 74 ++++++++++--------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/src/main/php/peer/ldap/util/LdapProtocol.class.php b/src/main/php/peer/ldap/util/LdapProtocol.class.php index 23c13480..ab2ec1e7 100755 --- a/src/main/php/peer/ldap/util/LdapProtocol.class.php +++ b/src/main/php/peer/ldap/util/LdapProtocol.class.php @@ -7,46 +7,54 @@ use peer\ldap\filter\Filters; class LdapProtocol { - const REQ_BIND = 0x60; - const REQ_UNBIND = 0x42; - const REQ_SEARCH = 0x63; - const REQ_MODIFY = 0x66; - const REQ_ADD = 0x68; - const REQ_DELETE = 0x4a; - const REQ_MODRDN = 0x6c; - const REQ_COMPARE = 0x6e; - const REQ_ABANDON = 0x50; - const REQ_EXTENSION = 0x77; - - const RES_BIND = 0x61; - const RES_SEARCH_ENTRY = 0x64; - const RES_SEARCH_REF = 0x73; - const RES_SEARCH = 0x65; - const RES_MODIFY = 0x67; - const RES_ADD = 0x69; - const RES_DELETE = 0x6b; - const RES_MODRDN = 0x6d; - const RES_COMPARE = 0x6f; - const RES_EXTENSION = 0x78; - - const SCOPE_BASE_OBJECT = 0; - const SCOPE_ONE_LEVEL = 1; - const SCOPE_SUBTREE = 2; - - const NEVER_DEREF_ALIASES = 0; - const DEREF_IN_SEARCHING = 1; - const DEREF_BASE_OBJECT = 2; - const DEREF_ALWAYS = 3; - - const STATUS_OK = 0; + const REQ_BIND = 0x60; + const REQ_UNBIND = 0x42; + const REQ_SEARCH = 0x63; + const REQ_MODIFY = 0x66; + const REQ_ADD = 0x68; + const REQ_DELETE = 0x4a; + const REQ_MODRDN = 0x6c; + const REQ_COMPARE = 0x6e; + const REQ_ABANDON = 0x50; + const REQ_EXTENSION = 0x77; + + const RES_BIND = 0x61; + const RES_SEARCH_ENTRY = 0x64; + const RES_SEARCH_REF = 0x73; + const RES_SEARCH = 0x65; + const RES_MODIFY = 0x67; + const RES_ADD = 0x69; + const RES_DELETE = 0x6b; + const RES_MODRDN = 0x6d; + const RES_COMPARE = 0x6f; + const RES_EXTENSION = 0x78; + + const SCOPE_BASE_OBJECT = 0x00; + const SCOPE_ONE_LEVEL = 0x01; + const SCOPE_SUBTREE = 0x02; + + const NEVER_DEREF_ALIASES = 0x00; + const DEREF_IN_SEARCHING = 0x01; + const DEREF_BASE_OBJECT = 0x02; + const DEREF_ALWAYS = 0x03; + + const STATUS_OK = 0x00; protected static $continue= [ self::RES_SEARCH_ENTRY => true, - self::RES_EXTENSION => true, + self::RES_EXTENSION => true, ]; protected $messageId= 0; + /** + * Instantiate protocol + * + * @param string $scheme + * @param string $host + * @param int $port + * @param var $params + */ public function __construct($scheme, $host, $port, $params) { if ('ldaps' === $scheme) { $this->sock= new SSLSocket($host, $port);