1<?php 2/** 3 * This file is part of the FreeDSx LDAP package. 4 * 5 * (c) Chad Sikorra <Chad.Sikorra@gmail.com> 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10 11namespace FreeDSx\Ldap\Protocol; 12 13use FreeDSx\Asn1\Asn1; 14use FreeDSx\Asn1\Type\AbstractType; 15use FreeDSx\Asn1\Type\IncompleteType; 16use FreeDSx\Asn1\Type\IntegerType; 17use FreeDSx\Asn1\Type\OctetStringType; 18use FreeDSx\Asn1\Type\SequenceOfType; 19use FreeDSx\Asn1\Type\SequenceType; 20use FreeDSx\Ldap\Control; 21use FreeDSx\Ldap\Control\ControlBag; 22use FreeDSx\Ldap\Exception\ProtocolException; 23use FreeDSx\Ldap\Operation\Request; 24use FreeDSx\Ldap\Operation\Response; 25use FreeDSx\Socket\PduInterface; 26 27/** 28 * The LDAP Message envelope (PDU). RFC 4511, 4.1.1 29 * 30 * LDAPMessage ::= SEQUENCE { 31 * messageID MessageID, 32 * protocolOp CHOICE { 33 * bindRequest BindRequest, 34 * bindResponse BindResponse, 35 * unbindRequest UnbindRequest, 36 * searchRequest SearchRequest, 37 * searchResEntry SearchResultEntry, 38 * searchResDone SearchResultDone, 39 * searchResRef SearchResultReference, 40 * modifyRequest ModifyRequest, 41 * modifyResponse ModifyResponse, 42 * addRequest AddRequest, 43 * addResponse AddResponse, 44 * delRequest DelRequest, 45 * delResponse DelResponse, 46 * modDNRequest ModifyDNRequest, 47 * modDNResponse ModifyDNResponse, 48 * compareRequest CompareRequest, 49 * compareResponse CompareResponse, 50 * abandonRequest AbandonRequest, 51 * extendedReq ExtendedRequest, 52 * extendedResp ExtendedResponse, 53 * ..., 54 * intermediateResponse IntermediateResponse }, 55 * controls [0] Controls OPTIONAL } 56 * 57 * MessageID ::= INTEGER (0 .. maxInt) 58 * 59 * maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) -- 60 * 61 * @author Chad Sikorra <Chad.Sikorra@gmail.com> 62 */ 63abstract class LdapMessage implements ProtocolElementInterface, PduInterface 64{ 65 /** 66 * @var int 67 */ 68 protected $messageId; 69 70 /** 71 * @var ControlBag 72 */ 73 protected $controls; 74 75 /** 76 * @param int $messageId 77 * @param Control\Control ...$controls 78 */ 79 public function __construct(int $messageId, Control\Control ...$controls) 80 { 81 $this->messageId = $messageId; 82 $this->controls = new ControlBag(...$controls); 83 } 84 85 /** 86 * @return int 87 */ 88 public function getMessageId(): int 89 { 90 return $this->messageId; 91 } 92 93 /** 94 * Get the controls for this specific message. 95 * 96 * @return ControlBag 97 */ 98 public function controls(): ControlBag 99 { 100 return $this->controls; 101 } 102 103 /** 104 * {@inheritdoc} 105 */ 106 public function toAsn1(): AbstractType 107 { 108 $asn1 = Asn1::sequence( 109 Asn1::integer($this->messageId), 110 $this->getOperationAsn1() 111 ); 112 113 if (\count($this->controls->toArray()) !== 0) { 114 /** @var SequenceOfType $controls */ 115 $controls = Asn1::context(0, Asn1::sequenceOf()); 116 foreach ($this->controls->toArray() as $control) { 117 $controls->addChild($control->toAsn1()); 118 } 119 $asn1->addChild($controls); 120 } 121 122 return $asn1; 123 } 124 125 /** 126 * {@inheritdoc} 127 */ 128 public static function fromAsn1(AbstractType $type) 129 { 130 if (!$type instanceof SequenceType) { 131 throw new ProtocolException(sprintf( 132 'Expected an ASN1 sequence type, but got: %s', 133 get_class($type) 134 )); 135 } 136 $count = \count($type->getChildren()); 137 if ($count < 2) { 138 throw new ProtocolException(sprintf( 139 'Expected an ASN1 sequence with at least 2 elements, but it has %s', 140 count($type->getChildren()) 141 )); 142 } 143 144 $controls = []; 145 if ($count > 2) { 146 for ($i = 2; $i < $count; $i++) { 147 $child = $type->getChild($i); 148 if ($child !== null && $child->getTagClass() === AbstractType::TAG_CLASS_CONTEXT_SPECIFIC && $child->getTagNumber() === 0) { 149 if (!$child instanceof IncompleteType) { 150 throw new ProtocolException('The ASN1 structure for the controls is malformed.'); 151 } 152 /** @var \FreeDSx\Asn1\Type\IncompleteType $child */ 153 $child = (new LdapEncoder())->complete($child, AbstractType::TAG_TYPE_SEQUENCE); 154 /** @var SequenceOfType $child */ 155 foreach ($child->getChildren() as $control) { 156 if (!($control instanceof SequenceType && $control->getChild(0) !== null && $control->getChild(0) instanceof OctetStringType)) { 157 throw new ProtocolException('The control either is not a sequence or has no OID value attached.'); 158 } 159 switch ($control->getChild(0)->getValue()) { 160 case Control\Control::OID_PAGING: 161 $controls[] = Control\PagingControl::fromAsn1($control); 162 break; 163 case Control\Control::OID_SORTING_RESPONSE: 164 $controls[] = Control\Sorting\SortingResponseControl::fromAsn1($control); 165 break; 166 case Control\Control::OID_VLV_RESPONSE: 167 $controls[] = Control\Vlv\VlvResponseControl::fromAsn1($control); 168 break; 169 case Control\Control::OID_DIR_SYNC: 170 $controls[] = Control\Ad\DirSyncResponseControl::fromAsn1($control); 171 break; 172 default: 173 $controls[] = Control\Control::fromAsn1($control); 174 break; 175 } 176 } 177 } 178 } 179 } 180 181 $messageId = $type->getChild(0); 182 if (!($messageId !== null && $messageId instanceof IntegerType)) { 183 throw new ProtocolException('Expected an LDAP message ID as an ASN.1 integer type. None received.'); 184 } 185 $opAsn1 = $type->getChild(1); 186 if ($opAsn1 === null) { 187 throw new ProtocolException('The LDAP message is malformed.'); 188 } 189 190 switch ($opAsn1->getTagNumber()) { 191 case 0: 192 $operation = Request\BindRequest::fromAsn1($opAsn1); 193 break; 194 case 1: 195 $operation = Response\BindResponse::fromAsn1($opAsn1); 196 break; 197 case 2: 198 $operation = Request\UnbindRequest::fromAsn1($opAsn1); 199 break; 200 case 3: 201 $operation = Request\SearchRequest::fromAsn1($opAsn1); 202 break; 203 case 4: 204 $operation = Response\SearchResultEntry::fromAsn1($opAsn1); 205 break; 206 case 5: 207 $operation = Response\SearchResultDone::fromAsn1($opAsn1); 208 break; 209 case 6: 210 $operation = Request\ModifyRequest::fromAsn1($opAsn1); 211 break; 212 case 7: 213 $operation = Response\ModifyResponse::fromAsn1($opAsn1); 214 break; 215 case 8: 216 $operation = Request\AddRequest::fromAsn1($opAsn1); 217 break; 218 case 9: 219 $operation = Response\AddResponse::fromAsn1($opAsn1); 220 break; 221 case 10: 222 $operation = Request\DeleteRequest::fromAsn1($opAsn1); 223 break; 224 case 11: 225 $operation = Response\DeleteResponse::fromAsn1($opAsn1); 226 break; 227 case 12: 228 $operation = Request\ModifyDnRequest::fromAsn1($opAsn1); 229 break; 230 case 13: 231 $operation = Response\ModifyDnResponse::fromAsn1($opAsn1); 232 break; 233 case 14: 234 $operation = Request\CompareRequest::fromAsn1($opAsn1); 235 break; 236 case 15: 237 $operation = Response\CompareResponse::fromAsn1($opAsn1); 238 break; 239 case 19: 240 $operation = Response\SearchResultReference::fromAsn1($opAsn1); 241 break; 242 case 23: 243 $operation = Request\ExtendedRequest::fromAsn1($opAsn1); 244 break; 245 case 24: 246 $operation = Response\ExtendedResponse::fromAsn1($opAsn1); 247 break; 248 case 25: 249 $operation = Response\IntermediateResponse::fromAsn1($opAsn1); 250 break; 251 default: 252 throw new ProtocolException(sprintf( 253 'The tag %s for the LDAP operation is not supported.', 254 $opAsn1->getTagNumber() 255 )); 256 } 257 258 return new static( 259 $messageId->getValue(), 260 $operation, 261 ...$controls 262 ); 263 } 264 265 /** 266 * @return AbstractType 267 */ 268 abstract protected function getOperationAsn1(): AbstractType; 269} 270