10b3fd2d3SAndreas Gohr<?php 2*dad993c5SAndreas Gohr 30b3fd2d3SAndreas Gohr/** 40b3fd2d3SAndreas Gohr * This file is part of the FreeDSx LDAP package. 50b3fd2d3SAndreas Gohr * 60b3fd2d3SAndreas Gohr * (c) Chad Sikorra <Chad.Sikorra@gmail.com> 70b3fd2d3SAndreas Gohr * 80b3fd2d3SAndreas Gohr * For the full copyright and license information, please view the LICENSE 90b3fd2d3SAndreas Gohr * file that was distributed with this source code. 100b3fd2d3SAndreas Gohr */ 110b3fd2d3SAndreas Gohr 120b3fd2d3SAndreas Gohrnamespace FreeDSx\Ldap\Protocol; 130b3fd2d3SAndreas Gohr 140b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Asn1; 15*dad993c5SAndreas Gohruse FreeDSx\Asn1\Exception\EncoderException; 16*dad993c5SAndreas Gohruse FreeDSx\Asn1\Exception\PartialPduException; 170b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\AbstractType; 180b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\IncompleteType; 190b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\IntegerType; 200b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\OctetStringType; 210b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\SequenceOfType; 220b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\SequenceType; 230b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Control; 240b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Control\ControlBag; 250b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Exception\ProtocolException; 26*dad993c5SAndreas Gohruse FreeDSx\Ldap\Exception\RuntimeException; 270b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Operation\Request; 280b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Operation\Response; 290b3fd2d3SAndreas Gohruse FreeDSx\Socket\PduInterface; 30*dad993c5SAndreas Gohruse function count; 310b3fd2d3SAndreas Gohr 320b3fd2d3SAndreas Gohr/** 330b3fd2d3SAndreas Gohr * The LDAP Message envelope (PDU). RFC 4511, 4.1.1 340b3fd2d3SAndreas Gohr * 350b3fd2d3SAndreas Gohr * LDAPMessage ::= SEQUENCE { 360b3fd2d3SAndreas Gohr * messageID MessageID, 370b3fd2d3SAndreas Gohr * protocolOp CHOICE { 380b3fd2d3SAndreas Gohr * bindRequest BindRequest, 390b3fd2d3SAndreas Gohr * bindResponse BindResponse, 400b3fd2d3SAndreas Gohr * unbindRequest UnbindRequest, 410b3fd2d3SAndreas Gohr * searchRequest SearchRequest, 420b3fd2d3SAndreas Gohr * searchResEntry SearchResultEntry, 430b3fd2d3SAndreas Gohr * searchResDone SearchResultDone, 440b3fd2d3SAndreas Gohr * searchResRef SearchResultReference, 450b3fd2d3SAndreas Gohr * modifyRequest ModifyRequest, 460b3fd2d3SAndreas Gohr * modifyResponse ModifyResponse, 470b3fd2d3SAndreas Gohr * addRequest AddRequest, 480b3fd2d3SAndreas Gohr * addResponse AddResponse, 490b3fd2d3SAndreas Gohr * delRequest DelRequest, 500b3fd2d3SAndreas Gohr * delResponse DelResponse, 510b3fd2d3SAndreas Gohr * modDNRequest ModifyDNRequest, 520b3fd2d3SAndreas Gohr * modDNResponse ModifyDNResponse, 530b3fd2d3SAndreas Gohr * compareRequest CompareRequest, 540b3fd2d3SAndreas Gohr * compareResponse CompareResponse, 550b3fd2d3SAndreas Gohr * abandonRequest AbandonRequest, 560b3fd2d3SAndreas Gohr * extendedReq ExtendedRequest, 570b3fd2d3SAndreas Gohr * extendedResp ExtendedResponse, 580b3fd2d3SAndreas Gohr * ..., 590b3fd2d3SAndreas Gohr * intermediateResponse IntermediateResponse }, 600b3fd2d3SAndreas Gohr * controls [0] Controls OPTIONAL } 610b3fd2d3SAndreas Gohr * 620b3fd2d3SAndreas Gohr * MessageID ::= INTEGER (0 .. maxInt) 630b3fd2d3SAndreas Gohr * 640b3fd2d3SAndreas Gohr * maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) -- 650b3fd2d3SAndreas Gohr * 660b3fd2d3SAndreas Gohr * @author Chad Sikorra <Chad.Sikorra@gmail.com> 670b3fd2d3SAndreas Gohr */ 680b3fd2d3SAndreas Gohrabstract class LdapMessage implements ProtocolElementInterface, PduInterface 690b3fd2d3SAndreas Gohr{ 700b3fd2d3SAndreas Gohr /** 710b3fd2d3SAndreas Gohr * @var int 720b3fd2d3SAndreas Gohr */ 730b3fd2d3SAndreas Gohr protected $messageId; 740b3fd2d3SAndreas Gohr 750b3fd2d3SAndreas Gohr /** 760b3fd2d3SAndreas Gohr * @var ControlBag 770b3fd2d3SAndreas Gohr */ 780b3fd2d3SAndreas Gohr protected $controls; 790b3fd2d3SAndreas Gohr 800b3fd2d3SAndreas Gohr /** 810b3fd2d3SAndreas Gohr * @param int $messageId 820b3fd2d3SAndreas Gohr * @param Control\Control ...$controls 830b3fd2d3SAndreas Gohr */ 840b3fd2d3SAndreas Gohr public function __construct(int $messageId, Control\Control ...$controls) 850b3fd2d3SAndreas Gohr { 860b3fd2d3SAndreas Gohr $this->messageId = $messageId; 870b3fd2d3SAndreas Gohr $this->controls = new ControlBag(...$controls); 880b3fd2d3SAndreas Gohr } 890b3fd2d3SAndreas Gohr 900b3fd2d3SAndreas Gohr /** 910b3fd2d3SAndreas Gohr * @return int 920b3fd2d3SAndreas Gohr */ 930b3fd2d3SAndreas Gohr public function getMessageId(): int 940b3fd2d3SAndreas Gohr { 950b3fd2d3SAndreas Gohr return $this->messageId; 960b3fd2d3SAndreas Gohr } 970b3fd2d3SAndreas Gohr 980b3fd2d3SAndreas Gohr /** 990b3fd2d3SAndreas Gohr * Get the controls for this specific message. 1000b3fd2d3SAndreas Gohr * 1010b3fd2d3SAndreas Gohr * @return ControlBag 1020b3fd2d3SAndreas Gohr */ 1030b3fd2d3SAndreas Gohr public function controls(): ControlBag 1040b3fd2d3SAndreas Gohr { 1050b3fd2d3SAndreas Gohr return $this->controls; 1060b3fd2d3SAndreas Gohr } 1070b3fd2d3SAndreas Gohr 1080b3fd2d3SAndreas Gohr /** 109*dad993c5SAndreas Gohr * @return AbstractType 110*dad993c5SAndreas Gohr * @psalm-return SequenceType 111*dad993c5SAndreas Gohr * @throws EncoderException 1120b3fd2d3SAndreas Gohr */ 1130b3fd2d3SAndreas Gohr public function toAsn1(): AbstractType 1140b3fd2d3SAndreas Gohr { 1150b3fd2d3SAndreas Gohr $asn1 = Asn1::sequence( 1160b3fd2d3SAndreas Gohr Asn1::integer($this->messageId), 1170b3fd2d3SAndreas Gohr $this->getOperationAsn1() 1180b3fd2d3SAndreas Gohr ); 1190b3fd2d3SAndreas Gohr 120*dad993c5SAndreas Gohr if (count($this->controls->toArray()) !== 0) { 1210b3fd2d3SAndreas Gohr /** @var SequenceOfType $controls */ 1220b3fd2d3SAndreas Gohr $controls = Asn1::context(0, Asn1::sequenceOf()); 1230b3fd2d3SAndreas Gohr foreach ($this->controls->toArray() as $control) { 1240b3fd2d3SAndreas Gohr $controls->addChild($control->toAsn1()); 1250b3fd2d3SAndreas Gohr } 1260b3fd2d3SAndreas Gohr $asn1->addChild($controls); 1270b3fd2d3SAndreas Gohr } 1280b3fd2d3SAndreas Gohr 1290b3fd2d3SAndreas Gohr return $asn1; 1300b3fd2d3SAndreas Gohr } 1310b3fd2d3SAndreas Gohr 1320b3fd2d3SAndreas Gohr /** 133*dad993c5SAndreas Gohr * {@inheritDoc} 134*dad993c5SAndreas Gohr * @return self 135*dad993c5SAndreas Gohr * @throws EncoderException 136*dad993c5SAndreas Gohr * @throws PartialPduException 137*dad993c5SAndreas Gohr * @throws RuntimeException 1380b3fd2d3SAndreas Gohr */ 1390b3fd2d3SAndreas Gohr public static function fromAsn1(AbstractType $type) 1400b3fd2d3SAndreas Gohr { 1410b3fd2d3SAndreas Gohr if (!$type instanceof SequenceType) { 1420b3fd2d3SAndreas Gohr throw new ProtocolException(sprintf( 1430b3fd2d3SAndreas Gohr 'Expected an ASN1 sequence type, but got: %s', 1440b3fd2d3SAndreas Gohr get_class($type) 1450b3fd2d3SAndreas Gohr )); 1460b3fd2d3SAndreas Gohr } 147*dad993c5SAndreas Gohr $count = count($type->getChildren()); 1480b3fd2d3SAndreas Gohr if ($count < 2) { 1490b3fd2d3SAndreas Gohr throw new ProtocolException(sprintf( 1500b3fd2d3SAndreas Gohr 'Expected an ASN1 sequence with at least 2 elements, but it has %s', 1510b3fd2d3SAndreas Gohr count($type->getChildren()) 1520b3fd2d3SAndreas Gohr )); 1530b3fd2d3SAndreas Gohr } 1540b3fd2d3SAndreas Gohr 1550b3fd2d3SAndreas Gohr $controls = []; 1560b3fd2d3SAndreas Gohr if ($count > 2) { 1570b3fd2d3SAndreas Gohr for ($i = 2; $i < $count; $i++) { 1580b3fd2d3SAndreas Gohr $child = $type->getChild($i); 1590b3fd2d3SAndreas Gohr if ($child !== null && $child->getTagClass() === AbstractType::TAG_CLASS_CONTEXT_SPECIFIC && $child->getTagNumber() === 0) { 1600b3fd2d3SAndreas Gohr if (!$child instanceof IncompleteType) { 1610b3fd2d3SAndreas Gohr throw new ProtocolException('The ASN1 structure for the controls is malformed.'); 1620b3fd2d3SAndreas Gohr } 1630b3fd2d3SAndreas Gohr /** @var SequenceOfType $child */ 164*dad993c5SAndreas Gohr $child = (new LdapEncoder())->complete($child, AbstractType::TAG_TYPE_SEQUENCE); 165*dad993c5SAndreas Gohr 1660b3fd2d3SAndreas Gohr foreach ($child->getChildren() as $control) { 1670b3fd2d3SAndreas Gohr if (!($control instanceof SequenceType && $control->getChild(0) !== null && $control->getChild(0) instanceof OctetStringType)) { 1680b3fd2d3SAndreas Gohr throw new ProtocolException('The control either is not a sequence or has no OID value attached.'); 1690b3fd2d3SAndreas Gohr } 1700b3fd2d3SAndreas Gohr switch ($control->getChild(0)->getValue()) { 1710b3fd2d3SAndreas Gohr case Control\Control::OID_PAGING: 1720b3fd2d3SAndreas Gohr $controls[] = Control\PagingControl::fromAsn1($control); 1730b3fd2d3SAndreas Gohr break; 1740b3fd2d3SAndreas Gohr case Control\Control::OID_SORTING_RESPONSE: 1750b3fd2d3SAndreas Gohr $controls[] = Control\Sorting\SortingResponseControl::fromAsn1($control); 1760b3fd2d3SAndreas Gohr break; 1770b3fd2d3SAndreas Gohr case Control\Control::OID_VLV_RESPONSE: 1780b3fd2d3SAndreas Gohr $controls[] = Control\Vlv\VlvResponseControl::fromAsn1($control); 1790b3fd2d3SAndreas Gohr break; 1800b3fd2d3SAndreas Gohr case Control\Control::OID_DIR_SYNC: 1810b3fd2d3SAndreas Gohr $controls[] = Control\Ad\DirSyncResponseControl::fromAsn1($control); 1820b3fd2d3SAndreas Gohr break; 1830b3fd2d3SAndreas Gohr default: 1840b3fd2d3SAndreas Gohr $controls[] = Control\Control::fromAsn1($control); 1850b3fd2d3SAndreas Gohr break; 1860b3fd2d3SAndreas Gohr } 1870b3fd2d3SAndreas Gohr } 1880b3fd2d3SAndreas Gohr } 1890b3fd2d3SAndreas Gohr } 1900b3fd2d3SAndreas Gohr } 1910b3fd2d3SAndreas Gohr 1920b3fd2d3SAndreas Gohr $messageId = $type->getChild(0); 1930b3fd2d3SAndreas Gohr if (!($messageId !== null && $messageId instanceof IntegerType)) { 1940b3fd2d3SAndreas Gohr throw new ProtocolException('Expected an LDAP message ID as an ASN.1 integer type. None received.'); 1950b3fd2d3SAndreas Gohr } 196*dad993c5SAndreas Gohr /** @var SequenceType|null $opAsn1 */ 1970b3fd2d3SAndreas Gohr $opAsn1 = $type->getChild(1); 1980b3fd2d3SAndreas Gohr if ($opAsn1 === null) { 1990b3fd2d3SAndreas Gohr throw new ProtocolException('The LDAP message is malformed.'); 2000b3fd2d3SAndreas Gohr } 2010b3fd2d3SAndreas Gohr 2020b3fd2d3SAndreas Gohr switch ($opAsn1->getTagNumber()) { 2030b3fd2d3SAndreas Gohr case 0: 2040b3fd2d3SAndreas Gohr $operation = Request\BindRequest::fromAsn1($opAsn1); 2050b3fd2d3SAndreas Gohr break; 2060b3fd2d3SAndreas Gohr case 1: 2070b3fd2d3SAndreas Gohr $operation = Response\BindResponse::fromAsn1($opAsn1); 2080b3fd2d3SAndreas Gohr break; 2090b3fd2d3SAndreas Gohr case 2: 2100b3fd2d3SAndreas Gohr $operation = Request\UnbindRequest::fromAsn1($opAsn1); 2110b3fd2d3SAndreas Gohr break; 2120b3fd2d3SAndreas Gohr case 3: 2130b3fd2d3SAndreas Gohr $operation = Request\SearchRequest::fromAsn1($opAsn1); 2140b3fd2d3SAndreas Gohr break; 2150b3fd2d3SAndreas Gohr case 4: 2160b3fd2d3SAndreas Gohr $operation = Response\SearchResultEntry::fromAsn1($opAsn1); 2170b3fd2d3SAndreas Gohr break; 2180b3fd2d3SAndreas Gohr case 5: 2190b3fd2d3SAndreas Gohr $operation = Response\SearchResultDone::fromAsn1($opAsn1); 2200b3fd2d3SAndreas Gohr break; 2210b3fd2d3SAndreas Gohr case 6: 2220b3fd2d3SAndreas Gohr $operation = Request\ModifyRequest::fromAsn1($opAsn1); 2230b3fd2d3SAndreas Gohr break; 2240b3fd2d3SAndreas Gohr case 7: 2250b3fd2d3SAndreas Gohr $operation = Response\ModifyResponse::fromAsn1($opAsn1); 2260b3fd2d3SAndreas Gohr break; 2270b3fd2d3SAndreas Gohr case 8: 2280b3fd2d3SAndreas Gohr $operation = Request\AddRequest::fromAsn1($opAsn1); 2290b3fd2d3SAndreas Gohr break; 2300b3fd2d3SAndreas Gohr case 9: 2310b3fd2d3SAndreas Gohr $operation = Response\AddResponse::fromAsn1($opAsn1); 2320b3fd2d3SAndreas Gohr break; 2330b3fd2d3SAndreas Gohr case 10: 2340b3fd2d3SAndreas Gohr $operation = Request\DeleteRequest::fromAsn1($opAsn1); 2350b3fd2d3SAndreas Gohr break; 2360b3fd2d3SAndreas Gohr case 11: 2370b3fd2d3SAndreas Gohr $operation = Response\DeleteResponse::fromAsn1($opAsn1); 2380b3fd2d3SAndreas Gohr break; 2390b3fd2d3SAndreas Gohr case 12: 2400b3fd2d3SAndreas Gohr $operation = Request\ModifyDnRequest::fromAsn1($opAsn1); 2410b3fd2d3SAndreas Gohr break; 2420b3fd2d3SAndreas Gohr case 13: 2430b3fd2d3SAndreas Gohr $operation = Response\ModifyDnResponse::fromAsn1($opAsn1); 2440b3fd2d3SAndreas Gohr break; 2450b3fd2d3SAndreas Gohr case 14: 2460b3fd2d3SAndreas Gohr $operation = Request\CompareRequest::fromAsn1($opAsn1); 2470b3fd2d3SAndreas Gohr break; 2480b3fd2d3SAndreas Gohr case 15: 2490b3fd2d3SAndreas Gohr $operation = Response\CompareResponse::fromAsn1($opAsn1); 2500b3fd2d3SAndreas Gohr break; 2510b3fd2d3SAndreas Gohr case 19: 2520b3fd2d3SAndreas Gohr $operation = Response\SearchResultReference::fromAsn1($opAsn1); 2530b3fd2d3SAndreas Gohr break; 2540b3fd2d3SAndreas Gohr case 23: 2550b3fd2d3SAndreas Gohr $operation = Request\ExtendedRequest::fromAsn1($opAsn1); 2560b3fd2d3SAndreas Gohr break; 2570b3fd2d3SAndreas Gohr case 24: 2580b3fd2d3SAndreas Gohr $operation = Response\ExtendedResponse::fromAsn1($opAsn1); 2590b3fd2d3SAndreas Gohr break; 2600b3fd2d3SAndreas Gohr case 25: 2610b3fd2d3SAndreas Gohr $operation = Response\IntermediateResponse::fromAsn1($opAsn1); 2620b3fd2d3SAndreas Gohr break; 2630b3fd2d3SAndreas Gohr default: 2640b3fd2d3SAndreas Gohr throw new ProtocolException(sprintf( 2650b3fd2d3SAndreas Gohr 'The tag %s for the LDAP operation is not supported.', 2660b3fd2d3SAndreas Gohr $opAsn1->getTagNumber() 2670b3fd2d3SAndreas Gohr )); 2680b3fd2d3SAndreas Gohr } 2690b3fd2d3SAndreas Gohr 2700b3fd2d3SAndreas Gohr return new static( 2710b3fd2d3SAndreas Gohr $messageId->getValue(), 2720b3fd2d3SAndreas Gohr $operation, 2730b3fd2d3SAndreas Gohr ...$controls 2740b3fd2d3SAndreas Gohr ); 2750b3fd2d3SAndreas Gohr } 2760b3fd2d3SAndreas Gohr 2770b3fd2d3SAndreas Gohr /** 2780b3fd2d3SAndreas Gohr * @return AbstractType 2790b3fd2d3SAndreas Gohr */ 2800b3fd2d3SAndreas Gohr abstract protected function getOperationAsn1(): AbstractType; 2810b3fd2d3SAndreas Gohr} 282