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