Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/scapy/layers/ldap.py: 43%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

838 statements  

1# SPDX-License-Identifier: GPL-2.0-only 

2# This file is part of Scapy 

3# See https://scapy.net/ for more information 

4# Copyright (C) Gabriel Potter <gabriel[]potter[]fr> 

5 

6""" 

7LDAP 

8 

9RFC 1777 - LDAP v2 

10RFC 4511 - LDAP v3 

11 

12Note: to mimic Microsoft Windows LDAP packets, you must set:: 

13 

14 conf.ASN1_default_long_size = 4 

15 

16.. note:: 

17 You will find more complete documentation for this layer over at 

18 `LDAP <https://scapy.readthedocs.io/en/latest/layers/ldap.html>`_ 

19""" 

20 

21import collections 

22import re 

23import socket 

24import ssl 

25import string 

26import struct 

27import uuid 

28 

29from scapy.arch import get_if_addr 

30from scapy.ansmachine import AnsweringMachine 

31from scapy.asn1.asn1 import ( 

32 ASN1_BOOLEAN, 

33 ASN1_Class, 

34 ASN1_Codecs, 

35 ASN1_ENUMERATED, 

36 ASN1_INTEGER, 

37 ASN1_STRING, 

38) 

39from scapy.asn1.ber import ( 

40 BER_Decoding_Error, 

41 BER_id_dec, 

42 BER_len_dec, 

43 BERcodec_STRING, 

44) 

45from scapy.asn1fields import ( 

46 ASN1F_badsequence, 

47 ASN1F_BOOLEAN, 

48 ASN1F_CHOICE, 

49 ASN1F_ENUMERATED, 

50 ASN1F_FLAGS, 

51 ASN1F_INTEGER, 

52 ASN1F_NULL, 

53 ASN1F_optional, 

54 ASN1F_PACKET, 

55 ASN1F_SEQUENCE_OF, 

56 ASN1F_SEQUENCE, 

57 ASN1F_SET_OF, 

58 ASN1F_STRING_PacketField, 

59 ASN1F_STRING, 

60) 

61from scapy.asn1packet import ASN1_Packet 

62from scapy.config import conf 

63from scapy.compat import StrEnum 

64from scapy.consts import WINDOWS 

65from scapy.error import log_runtime 

66from scapy.fields import ( 

67 FieldLenField, 

68 FlagsField, 

69 ThreeBytesField, 

70) 

71from scapy.packet import ( 

72 Packet, 

73 bind_bottom_up, 

74 bind_layers, 

75) 

76from scapy.sendrecv import send 

77from scapy.supersocket import ( 

78 SimpleSocket, 

79 StreamSocket, 

80 SSLStreamSocket, 

81) 

82 

83from scapy.layers.dns import dns_resolve 

84from scapy.layers.inet import IP, TCP, UDP 

85from scapy.layers.inet6 import IPv6 

86from scapy.layers.gssapi import ( 

87 _GSSAPI_Field, 

88 ChannelBindingType, 

89 GSS_C_FLAGS, 

90 GSS_C_NO_CHANNEL_BINDINGS, 

91 GSS_QOP_REQ_FLAGS, 

92 GSS_S_COMPLETE, 

93 GSS_S_CONTINUE_NEEDED, 

94 GSSAPI_BLOB_SIGNATURE, 

95 GSSAPI_BLOB, 

96 GssChannelBindings, 

97 SSP, 

98) 

99from scapy.layers.netbios import NBTDatagram 

100from scapy.layers.smb import ( 

101 NETLOGON, 

102 NETLOGON_SAM_LOGON_RESPONSE_EX, 

103) 

104from scapy.layers.windows.erref import STATUS_ERREF 

105 

106# Typing imports 

107from typing import ( 

108 Any, 

109 Dict, 

110 List, 

111 Optional, 

112 Union, 

113) 

114 

115# Elements of protocol 

116# https://datatracker.ietf.org/doc/html/rfc1777#section-4 

117 

118LDAPString = ASN1F_STRING 

119LDAPOID = ASN1F_STRING 

120LDAPDN = LDAPString 

121RelativeLDAPDN = LDAPString 

122AttributeType = LDAPString 

123AttributeValue = ASN1F_STRING 

124URI = LDAPString 

125 

126 

127class AttributeValueAssertion(ASN1_Packet): 

128 ASN1_codec = ASN1_Codecs.BER 

129 ASN1_root = ASN1F_SEQUENCE( 

130 AttributeType("attributeType", "organizationName"), 

131 AttributeValue("attributeValue", ""), 

132 ) 

133 

134 

135class LDAPReferral(ASN1_Packet): 

136 ASN1_codec = ASN1_Codecs.BER 

137 ASN1_root = LDAPString("uri", "") 

138 

139 

140LDAPResult = ( 

141 ASN1F_ENUMERATED( 

142 "resultCode", 

143 0, 

144 { 

145 0: "success", 

146 1: "operationsError", 

147 2: "protocolError", 

148 3: "timeLimitExceeded", 

149 4: "sizeLimitExceeded", 

150 5: "compareFalse", 

151 6: "compareTrue", 

152 7: "authMethodNotSupported", 

153 8: "strongAuthRequired", 

154 10: "referral", 

155 11: "adminLimitExceeded", 

156 14: "saslBindInProgress", 

157 16: "noSuchAttribute", 

158 17: "undefinedAttributeType", 

159 18: "inappropriateMatching", 

160 19: "constraintViolation", 

161 20: "attributeOrValueExists", 

162 21: "invalidAttributeSyntax", 

163 32: "noSuchObject", 

164 33: "aliasProblem", 

165 34: "invalidDNSyntax", 

166 35: "isLeaf", 

167 36: "aliasDereferencingProblem", 

168 48: "inappropriateAuthentication", 

169 49: "invalidCredentials", 

170 50: "insufficientAccessRights", 

171 51: "busy", 

172 52: "unavailable", 

173 53: "unwillingToPerform", 

174 54: "loopDetect", 

175 64: "namingViolation", 

176 65: "objectClassViolation", 

177 66: "notAllowedOnNonLeaf", 

178 67: "notAllowedOnRDN", 

179 68: "entryAlreadyExists", 

180 69: "objectClassModsProhibited", 

181 70: "resultsTooLarge", # CLDAP 

182 80: "other", 

183 }, 

184 ), 

185 LDAPDN("matchedDN", ""), 

186 LDAPString("diagnosticMessage", ""), 

187 # LDAP v3 only 

188 ASN1F_optional(ASN1F_SEQUENCE_OF("referral", [], LDAPReferral, implicit_tag=0xA3)), 

189) 

190 

191 

192# ldap APPLICATION 

193 

194 

195class ASN1_Class_LDAP(ASN1_Class): 

196 name = "LDAP" 

197 # APPLICATION + CONSTRUCTED = 0x40 | 0x20 

198 BindRequest = 0x60 

199 BindResponse = 0x61 

200 UnbindRequest = 0x42 # not constructed 

201 SearchRequest = 0x63 

202 SearchResultEntry = 0x64 

203 SearchResultDone = 0x65 

204 ModifyRequest = 0x66 

205 ModifyResponse = 0x67 

206 AddRequest = 0x68 

207 AddResponse = 0x69 

208 DelRequest = 0x4A # not constructed 

209 DelResponse = 0x6B 

210 ModifyDNRequest = 0x6C 

211 ModifyDNResponse = 0x6D 

212 CompareRequest = 0x6E 

213 CompareResponse = 0x7F 

214 AbandonRequest = 0x50 # application + primitive 

215 SearchResultReference = 0x73 

216 ExtendedRequest = 0x77 

217 ExtendedResponse = 0x78 

218 

219 

220# Bind operation 

221# https://datatracker.ietf.org/doc/html/rfc4511#section-4.2 

222 

223 

224class ASN1_Class_LDAP_Authentication(ASN1_Class): 

225 name = "LDAP Authentication" 

226 # CONTEXT-SPECIFIC = 0x80 

227 simple = 0x80 

228 krbv42LDAP = 0x81 

229 krbv42DSA = 0x82 

230 sasl = 0xA3 # CONTEXT-SPECIFIC | CONSTRUCTED 

231 # [MS-ADTS] sect 5.1.1.1 

232 sicilyPackageDiscovery = 0x89 

233 sicilyNegotiate = 0x8A 

234 sicilyResponse = 0x8B 

235 

236 

237# simple 

238class LDAP_Authentication_simple(ASN1_STRING): 

239 tag = ASN1_Class_LDAP_Authentication.simple 

240 

241 

242class BERcodec_LDAP_Authentication_simple(BERcodec_STRING): 

243 tag = ASN1_Class_LDAP_Authentication.simple 

244 

245 

246class ASN1F_LDAP_Authentication_simple(ASN1F_STRING): 

247 ASN1_tag = ASN1_Class_LDAP_Authentication.simple 

248 

249 

250# krbv42LDAP 

251class LDAP_Authentication_krbv42LDAP(ASN1_STRING): 

252 tag = ASN1_Class_LDAP_Authentication.krbv42LDAP 

253 

254 

255class BERcodec_LDAP_Authentication_krbv42LDAP(BERcodec_STRING): 

256 tag = ASN1_Class_LDAP_Authentication.krbv42LDAP 

257 

258 

259class ASN1F_LDAP_Authentication_krbv42LDAP(ASN1F_STRING): 

260 ASN1_tag = ASN1_Class_LDAP_Authentication.krbv42LDAP 

261 

262 

263# krbv42DSA 

264class LDAP_Authentication_krbv42DSA(ASN1_STRING): 

265 tag = ASN1_Class_LDAP_Authentication.krbv42DSA 

266 

267 

268class BERcodec_LDAP_Authentication_krbv42DSA(BERcodec_STRING): 

269 tag = ASN1_Class_LDAP_Authentication.krbv42DSA 

270 

271 

272class ASN1F_LDAP_Authentication_krbv42DSA(ASN1F_STRING): 

273 ASN1_tag = ASN1_Class_LDAP_Authentication.krbv42DSA 

274 

275 

276# sicilyPackageDiscovery 

277class LDAP_Authentication_sicilyPackageDiscovery(ASN1_STRING): 

278 tag = ASN1_Class_LDAP_Authentication.sicilyPackageDiscovery 

279 

280 

281class BERcodec_LDAP_Authentication_sicilyPackageDiscovery(BERcodec_STRING): 

282 tag = ASN1_Class_LDAP_Authentication.sicilyPackageDiscovery 

283 

284 

285class ASN1F_LDAP_Authentication_sicilyPackageDiscovery(ASN1F_STRING): 

286 ASN1_tag = ASN1_Class_LDAP_Authentication.sicilyPackageDiscovery 

287 

288 

289# sicilyNegotiate 

290class LDAP_Authentication_sicilyNegotiate(ASN1_STRING): 

291 tag = ASN1_Class_LDAP_Authentication.sicilyNegotiate 

292 

293 

294class BERcodec_LDAP_Authentication_sicilyNegotiate(BERcodec_STRING): 

295 tag = ASN1_Class_LDAP_Authentication.sicilyNegotiate 

296 

297 

298class ASN1F_LDAP_Authentication_sicilyNegotiate(ASN1F_STRING): 

299 ASN1_tag = ASN1_Class_LDAP_Authentication.sicilyNegotiate 

300 

301 

302# sicilyResponse 

303class LDAP_Authentication_sicilyResponse(ASN1_STRING): 

304 tag = ASN1_Class_LDAP_Authentication.sicilyResponse 

305 

306 

307class BERcodec_LDAP_Authentication_sicilyResponse(BERcodec_STRING): 

308 tag = ASN1_Class_LDAP_Authentication.sicilyResponse 

309 

310 

311class ASN1F_LDAP_Authentication_sicilyResponse(ASN1F_STRING): 

312 ASN1_tag = ASN1_Class_LDAP_Authentication.sicilyResponse 

313 

314 

315_SASL_MECHANISMS = {b"GSS-SPNEGO": GSSAPI_BLOB, b"GSSAPI": GSSAPI_BLOB} 

316 

317 

318class _SaslCredentialsField(ASN1F_STRING_PacketField): 

319 def m2i(self, pkt, s): 

320 val = super(_SaslCredentialsField, self).m2i(pkt, s) 

321 if not val[0].val: 

322 return val 

323 if pkt.mechanism.val in _SASL_MECHANISMS: 

324 return ( 

325 _SASL_MECHANISMS[pkt.mechanism.val](val[0].val, _underlayer=pkt), 

326 val[1], 

327 ) 

328 return val 

329 

330 

331class LDAP_Authentication_SaslCredentials(ASN1_Packet): 

332 ASN1_codec = ASN1_Codecs.BER 

333 ASN1_root = ASN1F_SEQUENCE( 

334 LDAPString("mechanism", ""), 

335 ASN1F_optional( 

336 _SaslCredentialsField("credentials", ""), 

337 ), 

338 implicit_tag=ASN1_Class_LDAP_Authentication.sasl, 

339 ) 

340 

341 

342class LDAP_BindRequest(ASN1_Packet): 

343 ASN1_codec = ASN1_Codecs.BER 

344 ASN1_root = ASN1F_SEQUENCE( 

345 ASN1F_INTEGER("version", 3), 

346 LDAPDN("bind_name", ""), 

347 ASN1F_CHOICE( 

348 "authentication", 

349 None, 

350 ASN1F_LDAP_Authentication_simple, 

351 ASN1F_LDAP_Authentication_krbv42LDAP, 

352 ASN1F_LDAP_Authentication_krbv42DSA, 

353 LDAP_Authentication_SaslCredentials, 

354 ), 

355 implicit_tag=ASN1_Class_LDAP.BindRequest, 

356 ) 

357 

358 

359class LDAP_BindResponse(ASN1_Packet): 

360 ASN1_codec = ASN1_Codecs.BER 

361 ASN1_root = ASN1F_SEQUENCE( 

362 *( 

363 LDAPResult 

364 + ( 

365 ASN1F_optional( 

366 # For GSSAPI, the response is wrapped in 

367 # LDAP_Authentication_SaslCredentials 

368 ASN1F_STRING("serverSaslCredsWrap", "", implicit_tag=0xA7), 

369 ), 

370 ASN1F_optional( 

371 ASN1F_STRING("serverSaslCreds", "", implicit_tag=0x87), 

372 ), 

373 ) 

374 ), 

375 implicit_tag=ASN1_Class_LDAP.BindResponse, 

376 ) 

377 

378 @property 

379 def serverCreds(self): 

380 """ 

381 serverCreds field in SicilyBindResponse 

382 """ 

383 return self.matchedDN.val 

384 

385 @serverCreds.setter 

386 def serverCreds(self, val): 

387 """ 

388 serverCreds field in SicilyBindResponse 

389 """ 

390 self.matchedDN = ASN1_STRING(val) 

391 

392 @property 

393 def serverSaslCredsData(self): 

394 """ 

395 Get serverSaslCreds or serverSaslCredsWrap depending on what's available 

396 """ 

397 if self.serverSaslCredsWrap and self.serverSaslCredsWrap.val: 

398 wrap = LDAP_Authentication_SaslCredentials(self.serverSaslCredsWrap.val) 

399 val = wrap.credentials 

400 if isinstance(val, ASN1_STRING): 

401 return val.val 

402 return bytes(val) 

403 elif self.serverSaslCreds and self.serverSaslCreds.val: 

404 return self.serverSaslCreds.val 

405 else: 

406 return None 

407 

408 

409# Unbind operation 

410# https://datatracker.ietf.org/doc/html/rfc4511#section-4.3 

411 

412 

413class LDAP_UnbindRequest(ASN1_Packet): 

414 ASN1_codec = ASN1_Codecs.BER 

415 ASN1_root = ASN1F_SEQUENCE( 

416 ASN1F_NULL("info", 0), 

417 implicit_tag=ASN1_Class_LDAP.UnbindRequest, 

418 ) 

419 

420 

421# Search operation 

422# https://datatracker.ietf.org/doc/html/rfc4511#section-4.5 

423 

424 

425class LDAP_SubstringFilterInitial(ASN1_Packet): 

426 ASN1_codec = ASN1_Codecs.BER 

427 ASN1_root = LDAPString("val", "") 

428 

429 

430class LDAP_SubstringFilterAny(ASN1_Packet): 

431 ASN1_codec = ASN1_Codecs.BER 

432 ASN1_root = LDAPString("val", "") 

433 

434 

435class LDAP_SubstringFilterFinal(ASN1_Packet): 

436 ASN1_codec = ASN1_Codecs.BER 

437 ASN1_root = LDAPString("val", "") 

438 

439 

440class LDAP_SubstringFilterStr(ASN1_Packet): 

441 ASN1_codec = ASN1_Codecs.BER 

442 ASN1_root = ASN1F_CHOICE( 

443 "str", 

444 ASN1_STRING(""), 

445 ASN1F_PACKET( 

446 "initial", 

447 LDAP_SubstringFilterInitial(), 

448 LDAP_SubstringFilterInitial, 

449 implicit_tag=0x80, 

450 ), 

451 ASN1F_PACKET( 

452 "any", LDAP_SubstringFilterAny(), LDAP_SubstringFilterAny, implicit_tag=0x81 

453 ), 

454 ASN1F_PACKET( 

455 "final", 

456 LDAP_SubstringFilterFinal(), 

457 LDAP_SubstringFilterFinal, 

458 implicit_tag=0x82, 

459 ), 

460 ) 

461 

462 

463class LDAP_SubstringFilter(ASN1_Packet): 

464 ASN1_codec = ASN1_Codecs.BER 

465 ASN1_root = ASN1F_SEQUENCE( 

466 AttributeType("type", ""), 

467 ASN1F_SEQUENCE_OF("filters", [], LDAP_SubstringFilterStr), 

468 ) 

469 

470 

471_LDAP_Filter = lambda *args, **kwargs: LDAP_Filter(*args, **kwargs) 

472 

473 

474class LDAP_FilterAnd(ASN1_Packet): 

475 ASN1_codec = ASN1_Codecs.BER 

476 ASN1_root = ASN1F_SET_OF("vals", [], _LDAP_Filter) 

477 

478 

479class LDAP_FilterOr(ASN1_Packet): 

480 ASN1_codec = ASN1_Codecs.BER 

481 ASN1_root = ASN1F_SET_OF("vals", [], _LDAP_Filter) 

482 

483 

484class LDAP_FilterNot(ASN1_Packet): 

485 ASN1_codec = ASN1_Codecs.BER 

486 ASN1_root = ASN1F_SEQUENCE( 

487 ASN1F_PACKET("val", None, None, next_cls_cb=lambda *args, **kwargs: LDAP_Filter) 

488 ) 

489 

490 

491class LDAP_FilterPresent(ASN1_Packet): 

492 ASN1_codec = ASN1_Codecs.BER 

493 ASN1_root = AttributeType("present", "objectClass") 

494 

495 

496class LDAP_FilterEqual(ASN1_Packet): 

497 ASN1_codec = ASN1_Codecs.BER 

498 ASN1_root = AttributeValueAssertion.ASN1_root 

499 

500 

501class LDAP_FilterGreaterOrEqual(ASN1_Packet): 

502 ASN1_codec = ASN1_Codecs.BER 

503 ASN1_root = AttributeValueAssertion.ASN1_root 

504 

505 

506class LDAP_FilterLessOrEqual(ASN1_Packet): 

507 ASN1_codec = ASN1_Codecs.BER 

508 ASN1_root = AttributeValueAssertion.ASN1_root 

509 

510 

511class LDAP_FilterApproxMatch(ASN1_Packet): 

512 ASN1_codec = ASN1_Codecs.BER 

513 ASN1_root = AttributeValueAssertion.ASN1_root 

514 

515 

516class LDAP_FilterExtensibleMatch(ASN1_Packet): 

517 ASN1_codec = ASN1_Codecs.BER 

518 ASN1_root = ASN1F_SEQUENCE( 

519 ASN1F_optional( 

520 LDAPString("matchingRule", "", implicit_tag=0x81), 

521 ), 

522 ASN1F_optional( 

523 LDAPString("type", "", implicit_tag=0x81), 

524 ), 

525 AttributeValue("matchValue", "", implicit_tag=0x82), 

526 ASN1F_BOOLEAN("dnAttributes", False, implicit_tag=0x84), 

527 ) 

528 

529 

530class ASN1_Class_LDAP_Filter(ASN1_Class): 

531 name = "LDAP Filter" 

532 # CONTEXT-SPECIFIC + CONSTRUCTED = 0x80 | 0x20 

533 And = 0xA0 

534 Or = 0xA1 

535 Not = 0xA2 

536 EqualityMatch = 0xA3 

537 Substrings = 0xA4 

538 GreaterOrEqual = 0xA5 

539 LessOrEqual = 0xA6 

540 Present = 0x87 # not constructed 

541 ApproxMatch = 0xA8 

542 ExtensibleMatch = 0xA9 

543 

544 

545class LDAP_Filter(ASN1_Packet): 

546 ASN1_codec = ASN1_Codecs.BER 

547 ASN1_root = ASN1F_CHOICE( 

548 "filter", 

549 LDAP_FilterPresent(), 

550 ASN1F_PACKET( 

551 "and_", None, LDAP_FilterAnd, implicit_tag=ASN1_Class_LDAP_Filter.And 

552 ), 

553 ASN1F_PACKET( 

554 "or_", None, LDAP_FilterOr, implicit_tag=ASN1_Class_LDAP_Filter.Or 

555 ), 

556 ASN1F_PACKET( 

557 "not_", None, LDAP_FilterNot, implicit_tag=ASN1_Class_LDAP_Filter.Not 

558 ), 

559 ASN1F_PACKET( 

560 "equalityMatch", 

561 None, 

562 LDAP_FilterEqual, 

563 implicit_tag=ASN1_Class_LDAP_Filter.EqualityMatch, 

564 ), 

565 ASN1F_PACKET( 

566 "substrings", 

567 None, 

568 LDAP_SubstringFilter, 

569 implicit_tag=ASN1_Class_LDAP_Filter.Substrings, 

570 ), 

571 ASN1F_PACKET( 

572 "greaterOrEqual", 

573 None, 

574 LDAP_FilterGreaterOrEqual, 

575 implicit_tag=ASN1_Class_LDAP_Filter.GreaterOrEqual, 

576 ), 

577 ASN1F_PACKET( 

578 "lessOrEqual", 

579 None, 

580 LDAP_FilterLessOrEqual, 

581 implicit_tag=ASN1_Class_LDAP_Filter.LessOrEqual, 

582 ), 

583 ASN1F_PACKET( 

584 "present", 

585 None, 

586 LDAP_FilterPresent, 

587 implicit_tag=ASN1_Class_LDAP_Filter.Present, 

588 ), 

589 ASN1F_PACKET( 

590 "approxMatch", 

591 None, 

592 LDAP_FilterApproxMatch, 

593 implicit_tag=ASN1_Class_LDAP_Filter.ApproxMatch, 

594 ), 

595 ASN1F_PACKET( 

596 "extensibleMatch", 

597 None, 

598 LDAP_FilterExtensibleMatch, 

599 implicit_tag=ASN1_Class_LDAP_Filter.ExtensibleMatch, 

600 ), 

601 ) 

602 

603 @staticmethod 

604 def from_rfc2254_string(filter: str): 

605 """ 

606 Convert a RFC-2254 filter to LDAP_Filter 

607 """ 

608 # Note: this code is very dumb to be readable. 

609 _lerr = "Invalid LDAP filter string: " 

610 if filter.lstrip()[0] != "(": 

611 filter = "(%s)" % filter 

612 

613 # 1. Cheap lexer. 

614 tokens = [] 

615 cur = tokens 

616 backtrack = [] 

617 filterlen = len(filter) 

618 i = 0 

619 while i < filterlen: 

620 c = filter[i] 

621 i += 1 

622 if c in [" ", "\t", "\n"]: 

623 # skip spaces 

624 continue 

625 elif c == "(": 

626 # enclosure 

627 cur.append([]) 

628 backtrack.append(cur) 

629 cur = cur[-1] 

630 elif c == ")": 

631 # end of enclosure 

632 if not backtrack: 

633 raise ValueError(_lerr + "parenthesis unmatched.") 

634 cur = backtrack.pop(-1) 

635 elif c in "&|!": 

636 # and / or / not 

637 cur.append(c) 

638 elif c in "=": 

639 # filtertype 

640 if cur[-1] in "~><:": 

641 cur[-1] += c 

642 continue 

643 cur.append(c) 

644 elif c in "~><": 

645 # comparisons 

646 cur.append(c) 

647 elif c == ":": 

648 # extensible 

649 cur.append(c) 

650 elif c == "*": 

651 # substring 

652 cur.append(c) 

653 else: 

654 # value 

655 v = "" 

656 for x in filter[i - 1 :]: 

657 if x in "():!|&~<>=*": 

658 break 

659 v += x 

660 if not v: 

661 raise ValueError(_lerr + "critical failure (impossible).") 

662 i += len(v) - 1 

663 cur.append(v) 

664 

665 # Check that parenthesis were closed 

666 if backtrack: 

667 raise ValueError(_lerr + "parenthesis unmatched.") 

668 

669 # LDAP filters must have an empty enclosure () 

670 tokens = tokens[0] 

671 

672 # 2. Cheap grammar parser. 

673 # Doing it recursively is trivial. 

674 def _getfld(x): 

675 if not x: 

676 raise ValueError(_lerr + "empty enclosure.") 

677 elif len(x) == 1 and isinstance(x[0], list): 

678 # useless enclosure 

679 return _getfld(x[0]) 

680 elif x[0] in "&|": 

681 # multinary operator 

682 if len(x) < 3: 

683 raise ValueError(_lerr + "bad use of multinary operator.") 

684 return (LDAP_FilterAnd if x[0] == "&" else LDAP_FilterOr)( 

685 vals=[LDAP_Filter(filter=_getfld(y)) for y in x[1:]] 

686 ) 

687 elif x[0] == "!": 

688 # unary operator 

689 if len(x) != 2: 

690 raise ValueError(_lerr + "bad use of unary operator.") 

691 return LDAP_FilterNot( 

692 val=LDAP_Filter(filter=_getfld(x[1])), 

693 ) 

694 elif "=" in x and "*" in x: 

695 # substring 

696 if len(x) < 3 or x[1] != "=": 

697 raise ValueError(_lerr + "bad use of substring.") 

698 return LDAP_SubstringFilter( 

699 type=ASN1_STRING(x[0].strip()), 

700 filters=[ 

701 LDAP_SubstringFilterStr( 

702 str=( 

703 LDAP_SubstringFilterFinal 

704 if i == (len(x) - 3) 

705 else ( 

706 LDAP_SubstringFilterInitial 

707 if i == 0 

708 else LDAP_SubstringFilterAny 

709 ) 

710 )(val=ASN1_STRING(y)) 

711 ) 

712 for i, y in enumerate(x[2:]) 

713 if y != "*" 

714 ], 

715 ) 

716 elif ":=" in x: 

717 # extensible 

718 raise NotImplementedError("Extensible not implemented.") 

719 elif any(y in ["<=", ">=", "~=", "="] for y in x): 

720 # simple 

721 if len(x) != 3 or "=" not in x[1]: 

722 raise ValueError(_lerr + "bad use of comparison.") 

723 if x[2] == "*": 

724 return LDAP_FilterPresent(present=ASN1_STRING(x[0])) 

725 return ( 

726 LDAP_FilterLessOrEqual 

727 if "<=" in x 

728 else ( 

729 LDAP_FilterGreaterOrEqual 

730 if ">=" in x 

731 else LDAP_FilterApproxMatch if "~=" in x else LDAP_FilterEqual 

732 ) 

733 )( 

734 attributeType=ASN1_STRING(x[0].strip()), 

735 attributeValue=ASN1_STRING(x[2]), 

736 ) 

737 else: 

738 raise ValueError(_lerr + "invalid filter.") 

739 

740 return LDAP_Filter(filter=_getfld(tokens)) 

741 

742 

743class LDAP_SearchRequestAttribute(ASN1_Packet): 

744 ASN1_codec = ASN1_Codecs.BER 

745 ASN1_root = AttributeType("type", "") 

746 

747 

748class LDAP_SearchRequest(ASN1_Packet): 

749 ASN1_codec = ASN1_Codecs.BER 

750 ASN1_root = ASN1F_SEQUENCE( 

751 LDAPDN("baseObject", ""), 

752 ASN1F_ENUMERATED( 

753 "scope", 0, {0: "baseObject", 1: "singleLevel", 2: "wholeSubtree"} 

754 ), 

755 ASN1F_ENUMERATED( 

756 "derefAliases", 

757 0, 

758 { 

759 0: "neverDerefAliases", 

760 1: "derefInSearching", 

761 2: "derefFindingBaseObj", 

762 3: "derefAlways", 

763 }, 

764 ), 

765 ASN1F_INTEGER("sizeLimit", 0), 

766 ASN1F_INTEGER("timeLimit", 0), 

767 ASN1F_BOOLEAN("attrsOnly", False), 

768 ASN1F_PACKET("filter", LDAP_Filter(), LDAP_Filter), 

769 ASN1F_SEQUENCE_OF("attributes", [], LDAP_SearchRequestAttribute), 

770 implicit_tag=ASN1_Class_LDAP.SearchRequest, 

771 ) 

772 

773 

774class LDAP_AttributeValue(ASN1_Packet): 

775 ASN1_codec = ASN1_Codecs.BER 

776 ASN1_root = AttributeValue("value", "") 

777 

778 

779class LDAP_PartialAttribute(ASN1_Packet): 

780 ASN1_codec = ASN1_Codecs.BER 

781 ASN1_root = ASN1F_SEQUENCE( 

782 AttributeType("type", ""), 

783 ASN1F_SET_OF("values", [], LDAP_AttributeValue), 

784 ) 

785 

786 

787class LDAP_SearchResponseEntry(ASN1_Packet): 

788 ASN1_codec = ASN1_Codecs.BER 

789 ASN1_root = ASN1F_SEQUENCE( 

790 LDAPDN("objectName", ""), 

791 ASN1F_SEQUENCE_OF( 

792 "attributes", 

793 LDAP_PartialAttribute(), 

794 LDAP_PartialAttribute, 

795 ), 

796 implicit_tag=ASN1_Class_LDAP.SearchResultEntry, 

797 ) 

798 

799 

800class LDAP_SearchResponseResultDone(ASN1_Packet): 

801 ASN1_codec = ASN1_Codecs.BER 

802 ASN1_root = ASN1F_SEQUENCE( 

803 *LDAPResult, 

804 implicit_tag=ASN1_Class_LDAP.SearchResultDone, 

805 ) 

806 

807 

808class LDAP_SearchResponseReference(ASN1_Packet): 

809 ASN1_codec = ASN1_Codecs.BER 

810 ASN1_root = ASN1F_SEQUENCE_OF( 

811 "uris", 

812 [], 

813 URI, 

814 implicit_tag=ASN1_Class_LDAP.SearchResultReference, 

815 ) 

816 

817 

818# Modify Operation 

819# https://datatracker.ietf.org/doc/html/rfc4511#section-4.6 

820 

821 

822class LDAP_ModifyRequestChange(ASN1_Packet): 

823 ASN1_codec = ASN1_Codecs.BER 

824 ASN1_root = ASN1F_SEQUENCE( 

825 ASN1F_ENUMERATED( 

826 "operation", 

827 0, 

828 { 

829 0: "add", 

830 1: "delete", 

831 2: "replace", 

832 }, 

833 ), 

834 ASN1F_PACKET("modification", LDAP_PartialAttribute(), LDAP_PartialAttribute), 

835 ) 

836 

837 

838class LDAP_ModifyRequest(ASN1_Packet): 

839 ASN1_codec = ASN1_Codecs.BER 

840 ASN1_root = ASN1F_SEQUENCE( 

841 LDAPDN("object", ""), 

842 ASN1F_SEQUENCE_OF("changes", [], LDAP_ModifyRequestChange), 

843 implicit_tag=ASN1_Class_LDAP.ModifyRequest, 

844 ) 

845 

846 

847class LDAP_ModifyResponse(ASN1_Packet): 

848 ASN1_codec = ASN1_Codecs.BER 

849 ASN1_root = ASN1F_SEQUENCE( 

850 *LDAPResult, 

851 implicit_tag=ASN1_Class_LDAP.ModifyResponse, 

852 ) 

853 

854 

855# Add Operation 

856# https://datatracker.ietf.org/doc/html/rfc4511#section-4.7 

857 

858 

859class LDAP_Attribute(ASN1_Packet): 

860 ASN1_codec = ASN1_Codecs.BER 

861 ASN1_root = LDAP_PartialAttribute.ASN1_root 

862 

863 

864class LDAP_AddRequest(ASN1_Packet): 

865 ASN1_codec = ASN1_Codecs.BER 

866 ASN1_root = ASN1F_SEQUENCE( 

867 LDAPDN("entry", ""), 

868 ASN1F_SEQUENCE_OF( 

869 "attributes", 

870 LDAP_Attribute(), 

871 LDAP_Attribute, 

872 ), 

873 implicit_tag=ASN1_Class_LDAP.AddRequest, 

874 ) 

875 

876 

877class LDAP_AddResponse(ASN1_Packet): 

878 ASN1_codec = ASN1_Codecs.BER 

879 ASN1_root = ASN1F_SEQUENCE( 

880 *LDAPResult, 

881 implicit_tag=ASN1_Class_LDAP.AddResponse, 

882 ) 

883 

884 

885# Delete Operation 

886# https://datatracker.ietf.org/doc/html/rfc4511#section-4.8 

887 

888 

889class LDAP_DelRequest(ASN1_Packet): 

890 ASN1_codec = ASN1_Codecs.BER 

891 ASN1_root = LDAPDN( 

892 "entry", 

893 "", 

894 implicit_tag=ASN1_Class_LDAP.DelRequest, 

895 ) 

896 

897 

898class LDAP_DelResponse(ASN1_Packet): 

899 ASN1_codec = ASN1_Codecs.BER 

900 ASN1_root = ASN1F_SEQUENCE( 

901 *LDAPResult, 

902 implicit_tag=ASN1_Class_LDAP.DelResponse, 

903 ) 

904 

905 

906# Modify DN Operation 

907# https://datatracker.ietf.org/doc/html/rfc4511#section-4.9 

908 

909 

910class LDAP_ModifyDNRequest(ASN1_Packet): 

911 ASN1_codec = ASN1_Codecs.BER 

912 ASN1_root = ASN1F_SEQUENCE( 

913 LDAPDN("entry", ""), 

914 LDAPDN("newrdn", ""), 

915 ASN1F_BOOLEAN("deleteoldrdn", ASN1_BOOLEAN(False)), 

916 ASN1F_optional(LDAPDN("newSuperior", None, implicit_tag=0xA0)), 

917 implicit_tag=ASN1_Class_LDAP.ModifyDNRequest, 

918 ) 

919 

920 

921class LDAP_ModifyDNResponse(ASN1_Packet): 

922 ASN1_codec = ASN1_Codecs.BER 

923 ASN1_root = ASN1F_SEQUENCE( 

924 *LDAPResult, 

925 implicit_tag=ASN1_Class_LDAP.ModifyDNResponse, 

926 ) 

927 

928 

929# Abandon Operation 

930# https://datatracker.ietf.org/doc/html/rfc4511#section-4.11 

931 

932 

933class LDAP_AbandonRequest(ASN1_Packet): 

934 ASN1_codec = ASN1_Codecs.BER 

935 ASN1_root = ASN1F_SEQUENCE( 

936 ASN1F_INTEGER("messageID", 0), 

937 implicit_tag=ASN1_Class_LDAP.AbandonRequest, 

938 ) 

939 

940 

941# LDAP v3 

942 

943# RFC 4511 sect 4.12 - Extended Operation 

944 

945 

946class LDAP_ExtendedResponse(ASN1_Packet): 

947 ASN1_codec = ASN1_Codecs.BER 

948 ASN1_root = ASN1F_SEQUENCE( 

949 *( 

950 LDAPResult 

951 + ( 

952 ASN1F_optional(LDAPOID("responseName", None, implicit_tag=0x8A)), 

953 ASN1F_optional(ASN1F_STRING("responseValue", None, implicit_tag=0x8B)), 

954 ) 

955 ), 

956 implicit_tag=ASN1_Class_LDAP.ExtendedResponse, 

957 ) 

958 

959 def do_dissect(self, x): 

960 # Note: Windows builds this packet with a buggy sequence size, that does not 

961 # include the optional fields. Do another pass of dissection on the optionals. 

962 s = super(LDAP_ExtendedResponse, self).do_dissect(x) 

963 if not s: 

964 return s 

965 for obj in self.ASN1_root.seq[-2:]: # only on the 2 optional fields 

966 try: 

967 s = obj.dissect(self, s) 

968 except ASN1F_badsequence: 

969 break 

970 return s 

971 

972 

973# RFC 4511 sect 4.1.11 

974 

975_LDAP_CONTROLS = {} 

976 

977 

978class _ControlValue_Field(ASN1F_STRING_PacketField): 

979 def m2i(self, pkt, s): 

980 val = super(_ControlValue_Field, self).m2i(pkt, s) 

981 if not val[0].val: 

982 return val 

983 controlType = pkt.controlType.val.decode() 

984 if controlType in _LDAP_CONTROLS: 

985 return ( 

986 _LDAP_CONTROLS[controlType](val[0].val, _underlayer=pkt), 

987 val[1], 

988 ) 

989 return val 

990 

991 

992class LDAP_Control(ASN1_Packet): 

993 ASN1_codec = ASN1_Codecs.BER 

994 ASN1_root = ASN1F_SEQUENCE( 

995 LDAPOID("controlType", ""), 

996 ASN1F_optional( 

997 ASN1F_BOOLEAN("criticality", False), 

998 ), 

999 ASN1F_optional(_ControlValue_Field("controlValue", "")), 

1000 ) 

1001 

1002 

1003# RFC 2696 - LDAP Control Extension for Simple Paged Results Manipulation 

1004 

1005 

1006class LDAP_realSearchControlValue(ASN1_Packet): 

1007 ASN1_codec = ASN1_Codecs.BER 

1008 ASN1_root = ASN1F_SEQUENCE( 

1009 ASN1F_INTEGER("size", 0), 

1010 ASN1F_STRING("cookie", ""), 

1011 ) 

1012 

1013 

1014_LDAP_CONTROLS["1.2.840.113556.1.4.319"] = LDAP_realSearchControlValue 

1015 

1016 

1017# [MS-ADTS] 

1018 

1019 

1020class LDAP_serverSDFlagsControl(ASN1_Packet): 

1021 ASN1_codec = ASN1_Codecs.BER 

1022 ASN1_root = ASN1F_SEQUENCE( 

1023 ASN1F_FLAGS( 

1024 "flags", 

1025 None, 

1026 [ 

1027 "OWNER", 

1028 "GROUP", 

1029 "DACL", 

1030 "SACL", 

1031 ], 

1032 ) 

1033 ) 

1034 

1035 

1036_LDAP_CONTROLS["1.2.840.113556.1.4.801"] = LDAP_serverSDFlagsControl 

1037 

1038 

1039# LDAP main class 

1040 

1041 

1042class LDAP(ASN1_Packet): 

1043 ASN1_codec = ASN1_Codecs.BER 

1044 ASN1_root = ASN1F_SEQUENCE( 

1045 ASN1F_INTEGER("messageID", 0), 

1046 ASN1F_CHOICE( 

1047 "protocolOp", 

1048 LDAP_SearchRequest(), 

1049 LDAP_BindRequest, 

1050 LDAP_BindResponse, 

1051 LDAP_SearchRequest, 

1052 LDAP_SearchResponseEntry, 

1053 LDAP_SearchResponseResultDone, 

1054 LDAP_AbandonRequest, 

1055 LDAP_SearchResponseReference, 

1056 LDAP_ModifyRequest, 

1057 LDAP_ModifyResponse, 

1058 LDAP_AddRequest, 

1059 LDAP_AddResponse, 

1060 LDAP_DelRequest, 

1061 LDAP_DelResponse, 

1062 LDAP_ModifyDNRequest, 

1063 LDAP_ModifyDNResponse, 

1064 LDAP_UnbindRequest, 

1065 LDAP_ExtendedResponse, 

1066 ), 

1067 # LDAP v3 only 

1068 ASN1F_optional( 

1069 ASN1F_SEQUENCE_OF("Controls", None, LDAP_Control, implicit_tag=0xA0) 

1070 ), 

1071 ) 

1072 

1073 show_indent = 0 

1074 

1075 @classmethod 

1076 def dispatch_hook(cls, _pkt=None, *args, **kargs): 

1077 if _pkt and len(_pkt) >= 4: 

1078 # Heuristic to detect SASL_Buffer 

1079 if _pkt[0] != 0x30: 

1080 if struct.unpack("!I", _pkt[:4])[0] + 4 == len(_pkt): 

1081 return LDAP_SASL_Buffer 

1082 return conf.raw_layer 

1083 return cls 

1084 

1085 @classmethod 

1086 def tcp_reassemble(cls, data, metadata, *args, **kwargs): 

1087 if len(data) < 4: 

1088 return None 

1089 # For LDAP, we would prefer to have the entire LDAP response 

1090 # (multiple LDAP concatenated) in one go, to stay consistent with 

1091 # what you get when using SASL. 

1092 remaining = data 

1093 while remaining: 

1094 try: 

1095 length, x = BER_len_dec(BER_id_dec(remaining)[1]) 

1096 except (BER_Decoding_Error, IndexError): 

1097 return None 

1098 if length and len(x) >= length: 

1099 remaining = x[length:] 

1100 if not remaining: 

1101 pkt = cls(data) 

1102 # Packet can be a whole response yet still miss some content. 

1103 if ( 

1104 LDAP_SearchResponseEntry in pkt 

1105 and LDAP_SearchResponseResultDone not in pkt 

1106 ): 

1107 return None 

1108 return pkt 

1109 else: 

1110 if length: 

1111 metadata["tcp_min_len"] = len(data) - len(x) + length 

1112 return None 

1113 return None 

1114 

1115 def hashret(self): 

1116 return b"ldap" 

1117 

1118 @property 

1119 def unsolicited(self): 

1120 # RFC4511 sect 4.4. - Unsolicited Notification 

1121 return self.messageID == 0 and isinstance( 

1122 self.protocolOp, LDAP_ExtendedResponse 

1123 ) 

1124 

1125 def answers(self, other): 

1126 if self.unsolicited: 

1127 return True 

1128 return isinstance(other, LDAP) and other.messageID == self.messageID 

1129 

1130 def mysummary(self): 

1131 if not self.protocolOp or not self.messageID: 

1132 return "" 

1133 return ( 

1134 "%s(%s)" 

1135 % ( 

1136 self.protocolOp.__class__.__name__.replace("_", " "), 

1137 self.messageID.val, 

1138 ), 

1139 [LDAP], 

1140 ) 

1141 

1142 

1143bind_layers(LDAP, LDAP) 

1144 

1145bind_bottom_up(TCP, LDAP, dport=389) 

1146bind_bottom_up(TCP, LDAP, sport=389) 

1147bind_bottom_up(TCP, LDAP, dport=3268) 

1148bind_bottom_up(TCP, LDAP, sport=3268) 

1149bind_layers(TCP, LDAP, sport=389, dport=389) 

1150 

1151# CLDAP - rfc1798 

1152 

1153 

1154class CLDAP(ASN1_Packet): 

1155 ASN1_codec = ASN1_Codecs.BER 

1156 ASN1_root = ASN1F_SEQUENCE( 

1157 LDAP.ASN1_root.seq[0], # messageID 

1158 ASN1F_optional( 

1159 LDAPDN("user", ""), 

1160 ), 

1161 LDAP.ASN1_root.seq[1], # protocolOp 

1162 ) 

1163 

1164 def answers(self, other): 

1165 return isinstance(other, CLDAP) and other.messageID == self.messageID 

1166 

1167 

1168bind_layers(CLDAP, CLDAP) 

1169 

1170bind_bottom_up(UDP, CLDAP, dport=389) 

1171bind_bottom_up(UDP, CLDAP, sport=389) 

1172bind_layers(UDP, CLDAP, sport=389, dport=389) 

1173 

1174# [MS-ADTS] sect 3.1.1.2.3.3 

1175 

1176LDAP_PROPERTY_SET = { 

1177 uuid.UUID( 

1178 "C7407360-20BF-11D0-A768-00AA006E0529" 

1179 ): "Domain Password & Lockout Policies", 

1180 uuid.UUID("59BA2F42-79A2-11D0-9020-00C04FC2D3CF"): "General Information", 

1181 uuid.UUID("4C164200-20C0-11D0-A768-00AA006E0529"): "Account Restrictions", 

1182 uuid.UUID("5F202010-79A5-11D0-9020-00C04FC2D4CF"): "Logon Information", 

1183 uuid.UUID("BC0AC240-79A9-11D0-9020-00C04FC2D4CF"): "Group Membership", 

1184 uuid.UUID("E45795B2-9455-11D1-AEBD-0000F80367C1"): "Phone and Mail Options", 

1185 uuid.UUID("77B5B886-944A-11D1-AEBD-0000F80367C1"): "Personal Information", 

1186 uuid.UUID("E45795B3-9455-11D1-AEBD-0000F80367C1"): "Web Information", 

1187 uuid.UUID("E48D0154-BCF8-11D1-8702-00C04FB96050"): "Public Information", 

1188 uuid.UUID("037088F8-0AE1-11D2-B422-00A0C968F939"): "Remote Access Information", 

1189 uuid.UUID("B8119FD0-04F6-4762-AB7A-4986C76B3F9A"): "Other Domain Parameters", 

1190 uuid.UUID("72E39547-7B18-11D1-ADEF-00C04FD8D5CD"): "DNS Host Name Attributes", 

1191 uuid.UUID("FFA6F046-CA4B-4FEB-B40D-04DFEE722543"): "MS-TS-GatewayAccess", 

1192 uuid.UUID("91E647DE-D96F-4B70-9557-D63FF4F3CCD8"): "Private Information", 

1193 uuid.UUID("5805BC62-BDC9-4428-A5E2-856A0F4C185E"): "Terminal Server License Server", 

1194} 

1195 

1196# [MS-ADTS] sect 5.1.3.2.1 

1197 

1198LDAP_CONTROL_ACCESS_RIGHTS = { 

1199 uuid.UUID("ee914b82-0a98-11d1-adbb-00c04fd8d5cd"): "Abandon-Replication", 

1200 uuid.UUID("440820ad-65b4-11d1-a3da-0000f875ae0d"): "Add-GUID", 

1201 uuid.UUID("1abd7cf8-0a99-11d1-adbb-00c04fd8d5cd"): "Allocate-Rids", 

1202 uuid.UUID("68b1d179-0d15-4d4f-ab71-46152e79a7bc"): "Allowed-To-Authenticate", 

1203 uuid.UUID("edacfd8f-ffb3-11d1-b41d-00a0c968f939"): "Apply-Group-Policy", 

1204 uuid.UUID("0e10c968-78fb-11d2-90d4-00c04f79dc55"): "Certificate-Enrollment", 

1205 uuid.UUID("a05b8cc2-17bc-4802-a710-e7c15ab866a2"): "Certificate-AutoEnrollment", 

1206 uuid.UUID("014bf69c-7b3b-11d1-85f6-08002be74fab"): "Change-Domain-Master", 

1207 uuid.UUID("cc17b1fb-33d9-11d2-97d4-00c04fd8d5cd"): "Change-Infrastructure-Master", 

1208 uuid.UUID("bae50096-4752-11d1-9052-00c04fc2d4cf"): "Change-PDC", 

1209 uuid.UUID("d58d5f36-0a98-11d1-adbb-00c04fd8d5cd"): "Change-Rid-Master", 

1210 uuid.UUID("e12b56b6-0a95-11d1-adbb-00c04fd8d5cd"): "Change-Schema-Master", 

1211 uuid.UUID("e2a36dc9-ae17-47c3-b58b-be34c55ba633"): "Create-Inbound-Forest-Trust", 

1212 uuid.UUID("fec364e0-0a98-11d1-adbb-00c04fd8d5cd"): "Do-Garbage-Collection", 

1213 uuid.UUID("ab721a52-1e2f-11d0-9819-00aa0040529b"): "Domain-Administer-Server", 

1214 uuid.UUID("69ae6200-7f46-11d2-b9ad-00c04f79f805"): "DS-Check-Stale-Phantoms", 

1215 uuid.UUID("2f16c4a5-b98e-432c-952a-cb388ba33f2e"): "DS-Execute-Intentions-Script", 

1216 uuid.UUID("9923a32a-3607-11d2-b9be-0000f87a36b2"): "DS-Install-Replica", 

1217 uuid.UUID("4ecc03fe-ffc0-4947-b630-eb672a8a9dbc"): "DS-Query-Self-Quota", 

1218 uuid.UUID("1131f6aa-9c07-11d1-f79f-00c04fc2dcd2"): "DS-Replication-Get-Changes", 

1219 uuid.UUID("1131f6ad-9c07-11d1-f79f-00c04fc2dcd2"): "DS-Replication-Get-Changes-All", 

1220 uuid.UUID( 

1221 "89e95b76-444d-4c62-991a-0facbeda640c" 

1222 ): "DS-Replication-Get-Changes-In-Filtered-Set", 

1223 uuid.UUID("1131f6ac-9c07-11d1-f79f-00c04fc2dcd2"): "DS-Replication-Manage-Topology", 

1224 uuid.UUID( 

1225 "f98340fb-7c5b-4cdb-a00b-2ebdfa115a96" 

1226 ): "DS-Replication-Monitor-Topology", 

1227 uuid.UUID("1131f6ab-9c07-11d1-f79f-00c04fc2dcd2"): "DS-Replication-Synchronize", 

1228 uuid.UUID( 

1229 "05c74c5e-4deb-43b4-bd9f-86664c2a7fd5" 

1230 ): "Enable-Per-User-Reversibly-Encrypted-Password", 

1231 uuid.UUID("b7b1b3de-ab09-4242-9e30-9980e5d322f7"): "Generate-RSoP-Logging", 

1232 uuid.UUID("b7b1b3dd-ab09-4242-9e30-9980e5d322f7"): "Generate-RSoP-Planning", 

1233 uuid.UUID("7c0e2a7c-a419-48e4-a995-10180aad54dd"): "Manage-Optional-Features", 

1234 uuid.UUID("ba33815a-4f93-4c76-87f3-57574bff8109"): "Migrate-SID-History", 

1235 uuid.UUID("b4e60130-df3f-11d1-9c86-006008764d0e"): "msmq-Open-Connector", 

1236 uuid.UUID("06bd3201-df3e-11d1-9c86-006008764d0e"): "msmq-Peek", 

1237 uuid.UUID("4b6e08c3-df3c-11d1-9c86-006008764d0e"): "msmq-Peek-computer-Journal", 

1238 uuid.UUID("4b6e08c1-df3c-11d1-9c86-006008764d0e"): "msmq-Peek-Dead-Letter", 

1239 uuid.UUID("06bd3200-df3e-11d1-9c86-006008764d0e"): "msmq-Receive", 

1240 uuid.UUID("4b6e08c2-df3c-11d1-9c86-006008764d0e"): "msmq-Receive-computer-Journal", 

1241 uuid.UUID("4b6e08c0-df3c-11d1-9c86-006008764d0e"): "msmq-Receive-Dead-Letter", 

1242 uuid.UUID("06bd3203-df3e-11d1-9c86-006008764d0e"): "msmq-Receive-journal", 

1243 uuid.UUID("06bd3202-df3e-11d1-9c86-006008764d0e"): "msmq-Send", 

1244 uuid.UUID("a1990816-4298-11d1-ade2-00c04fd8d5cd"): "Open-Address-Book", 

1245 uuid.UUID( 

1246 "1131f6ae-9c07-11d1-f79f-00c04fc2dcd2" 

1247 ): "Read-Only-Replication-Secret-Synchronization", 

1248 uuid.UUID("45ec5156-db7e-47bb-b53f-dbeb2d03c40f"): "Reanimate-Tombstones", 

1249 uuid.UUID("0bc1554e-0a99-11d1-adbb-00c04fd8d5cd"): "Recalculate-Hierarchy", 

1250 uuid.UUID( 

1251 "62dd28a8-7f46-11d2-b9ad-00c04f79f805" 

1252 ): "Recalculate-Security-Inheritance", 

1253 uuid.UUID("ab721a56-1e2f-11d0-9819-00aa0040529b"): "Receive-As", 

1254 uuid.UUID("9432c620-033c-4db7-8b58-14ef6d0bf477"): "Refresh-Group-Cache", 

1255 uuid.UUID("1a60ea8d-58a6-4b20-bcdc-fb71eb8a9ff8"): "Reload-SSL-Certificate", 

1256 uuid.UUID("7726b9d5-a4b4-4288-a6b2-dce952e80a7f"): "Run-Protect_Admin_Groups-Task", 

1257 uuid.UUID("91d67418-0135-4acc-8d79-c08e857cfbec"): "SAM-Enumerate-Entire-Domain", 

1258 uuid.UUID("ab721a54-1e2f-11d0-9819-00aa0040529b"): "Send-As", 

1259 uuid.UUID("ab721a55-1e2f-11d0-9819-00aa0040529b"): "Send-To", 

1260 uuid.UUID("ccc2dc7d-a6ad-4a7a-8846-c04e3cc53501"): "Unexpire-Password", 

1261 uuid.UUID( 

1262 "280f369c-67c7-438e-ae98-1d46f3c6f541" 

1263 ): "Update-Password-Not-Required-Bit", 

1264 uuid.UUID("be2bb760-7f46-11d2-b9ad-00c04f79f805"): "Update-Schema-Cache", 

1265 uuid.UUID("ab721a53-1e2f-11d0-9819-00aa0040529b"): "User-Change-Password", 

1266 uuid.UUID("00299570-246d-11d0-a768-00aa006e0529"): "User-Force-Change-Password", 

1267 uuid.UUID("3e0f7e18-2c7a-4c10-ba82-4d926db99a3e"): "DS-Clone-Domain-Controller", 

1268 uuid.UUID("084c93a2-620d-4879-a836-f0ae47de0e89"): "DS-Read-Partition-Secrets", 

1269 uuid.UUID("94825a8d-b171-4116-8146-1e34d8f54401"): "DS-Write-Partition-Secrets", 

1270 uuid.UUID("4125c71f-7fac-4ff0-bcb7-f09a41325286"): "DS-Set-Owner", 

1271 uuid.UUID("88a9933e-e5c8-4f2a-9dd7-2527416b8092"): "DS-Bypass-Quota", 

1272 uuid.UUID("9b026da6-0d3c-465c-8bee-5199d7165cba"): "DS-Validated-Write-Computer", 

1273} 

1274 

1275# [MS-ADTS] sect 5.1.3.2 and 

1276# https://learn.microsoft.com/en-us/windows/win32/secauthz/directory-services-access-rights 

1277 

1278LDAP_DS_ACCESS_RIGHTS = { 

1279 0x00000001: "CREATE_CHILD", 

1280 0x00000002: "DELETE_CHILD", 

1281 0x00000004: "LIST_CONTENTS", 

1282 0x00000008: "WRITE_PROPERTY_EXTENDED", 

1283 0x00000010: "READ_PROP", 

1284 0x00000020: "WRITE_PROP", 

1285 0x00000040: "DELETE_TREE", 

1286 0x00000080: "LIST_OBJECT", 

1287 0x00000100: "CONTROL_ACCESS", 

1288 0x00010000: "DELETE", 

1289 0x00020000: "READ_CONTROL", 

1290 0x00040000: "WRITE_DAC", 

1291 0x00080000: "WRITE_OWNER", 

1292 0x00100000: "SYNCHRONIZE", 

1293 0x01000000: "ACCESS_SYSTEM_SECURITY", 

1294 0x80000000: "GENERIC_READ", 

1295 0x40000000: "GENERIC_WRITE", 

1296 0x20000000: "GENERIC_EXECUTE", 

1297 0x10000000: "GENERIC_ALL", 

1298} 

1299 

1300 

1301# Small CLDAP Answering machine: [MS-ADTS] 6.3.3 - Ldap ping 

1302 

1303 

1304class LdapPing_am(AnsweringMachine): 

1305 function_name = "ldappingd" 

1306 filter = "udp port 389 or 138" 

1307 send_function = staticmethod(send) 

1308 

1309 def parse_options( 

1310 self, 

1311 NetbiosDomainName="DOMAIN", 

1312 DomainGuid=uuid.UUID("192bc4b3-0085-4521-83fe-062913ef59f2"), 

1313 DcSiteName="Default-First-Site-Name", 

1314 NetbiosComputerName="SRV1", 

1315 DnsForestName=None, 

1316 DnsHostName=None, 

1317 src_ip=None, 

1318 src_ip6=None, 

1319 ): 

1320 self.NetbiosDomainName = NetbiosDomainName 

1321 self.DnsForestName = DnsForestName or (NetbiosDomainName + ".LOCAL") 

1322 self.DomainGuid = DomainGuid 

1323 self.DcSiteName = DcSiteName 

1324 self.NetbiosComputerName = NetbiosComputerName 

1325 self.DnsHostName = DnsHostName or ( 

1326 NetbiosComputerName + "." + self.DnsForestName 

1327 ) 

1328 self.src_ip = src_ip 

1329 self.src_ip6 = src_ip6 

1330 

1331 def is_request(self, req): 

1332 # [MS-ADTS] 6.3.3 - Example: 

1333 # (&(DnsDomain=abcde.corp.microsoft.com)(Host=abcdefgh-dev)(User=abcdefgh- 

1334 # dev$)(AAC=\80\00\00\00)(DomainGuid=\3b\b0\21\ca\d3\6d\d1\11\8a\7d\b8\df\b1\56\87\1f)(NtVer 

1335 # =\06\00\00\00)) 

1336 if NBTDatagram in req: 

1337 # special case: mailslot ping 

1338 from scapy.layers.smb import SMBMailslot_Write, NETLOGON_SAM_LOGON_REQUEST 

1339 

1340 try: 

1341 return ( 

1342 SMBMailslot_Write in req and NETLOGON_SAM_LOGON_REQUEST in req.Data 

1343 ) 

1344 except AttributeError: 

1345 return False 

1346 if CLDAP not in req or not isinstance(req.protocolOp, LDAP_SearchRequest): 

1347 return False 

1348 req = req.protocolOp 

1349 return ( 

1350 req.attributes 

1351 and req.attributes[0].type.val.lower() == b"netlogon" 

1352 and req.filter 

1353 and isinstance(req.filter.filter, LDAP_FilterAnd) 

1354 and any( 

1355 x.filter.attributeType.val == b"NtVer" for x in req.filter.filter.vals 

1356 ) 

1357 ) 

1358 

1359 def make_reply(self, req): 

1360 if NBTDatagram in req: 

1361 # Special case 

1362 return self.make_mailslot_ping_reply(req) 

1363 if IPv6 in req: 

1364 resp = IPv6(dst=req[IPv6].src, src=self.src_ip6 or req[IPv6].dst) 

1365 else: 

1366 resp = IP(dst=req[IP].src, src=self.src_ip or req[IP].dst) 

1367 resp /= UDP(sport=req.dport, dport=req.sport) 

1368 # get the DnsDomainName from the request 

1369 try: 

1370 DnsDomainName = next( 

1371 x.filter.attributeValue.val 

1372 for x in req.protocolOp.filter.filter.vals 

1373 if x.filter.attributeType.val == b"DnsDomain" 

1374 ) 

1375 except StopIteration: 

1376 return 

1377 return ( 

1378 resp 

1379 / CLDAP( 

1380 protocolOp=LDAP_SearchResponseEntry( 

1381 attributes=[ 

1382 LDAP_PartialAttribute( 

1383 values=[ 

1384 LDAP_AttributeValue( 

1385 value=ASN1_STRING( 

1386 val=bytes( 

1387 NETLOGON_SAM_LOGON_RESPONSE_EX( 

1388 # Mandatory fields 

1389 DnsDomainName=DnsDomainName, 

1390 NtVersion="V1+V5", 

1391 LmNtToken=65535, 

1392 Lm20Token=65535, 

1393 # Below can be customized 

1394 Flags=0x3F3FD, 

1395 DomainGuid=self.DomainGuid, 

1396 DnsForestName=self.DnsForestName, 

1397 DnsHostName=self.DnsHostName, 

1398 NetbiosDomainName=self.NetbiosDomainName, # noqa: E501 

1399 NetbiosComputerName=self.NetbiosComputerName, # noqa: E501 

1400 UserName=b".", 

1401 DcSiteName=self.DcSiteName, 

1402 ClientSiteName=self.DcSiteName, 

1403 ) 

1404 ) 

1405 ) 

1406 ) 

1407 ], 

1408 type=ASN1_STRING(b"Netlogon"), 

1409 ) 

1410 ], 

1411 ), 

1412 messageID=req.messageID, 

1413 user=None, 

1414 ) 

1415 / CLDAP( 

1416 protocolOp=LDAP_SearchResponseResultDone( 

1417 referral=None, 

1418 resultCode=0, 

1419 ), 

1420 messageID=req.messageID, 

1421 user=None, 

1422 ) 

1423 ) 

1424 

1425 def make_mailslot_ping_reply(self, req): 

1426 # type: (Packet) -> Packet 

1427 from scapy.layers.smb import ( 

1428 SMBMailslot_Write, 

1429 SMB_Header, 

1430 DcSockAddr, 

1431 NETLOGON_SAM_LOGON_RESPONSE_EX, 

1432 ) 

1433 

1434 resp = IP(dst=req[IP].src) / UDP( 

1435 sport=req.dport, 

1436 dport=req.sport, 

1437 ) 

1438 address = self.src_ip or get_if_addr(self.optsniff.get("iface", conf.iface)) 

1439 resp /= ( 

1440 NBTDatagram( 

1441 SourceName=req.DestinationName, 

1442 SUFFIX1=req.SUFFIX2, 

1443 DestinationName=req.SourceName, 

1444 SUFFIX2=req.SUFFIX1, 

1445 SourceIP=address, 

1446 ) 

1447 / SMB_Header() 

1448 / SMBMailslot_Write( 

1449 Name=req.Data.MailslotName, 

1450 ) 

1451 ) 

1452 NetbiosDomainName = req.DestinationName.strip() 

1453 resp.Data = NETLOGON_SAM_LOGON_RESPONSE_EX( 

1454 # Mandatory fields 

1455 NetbiosDomainName=NetbiosDomainName, 

1456 DcSockAddr=DcSockAddr( 

1457 sin_addr=address, 

1458 ), 

1459 NtVersion="V1+V5EX+V5EX_WITH_IP", 

1460 LmNtToken=65535, 

1461 Lm20Token=65535, 

1462 # Below can be customized 

1463 Flags=0x3F3FD, 

1464 DomainGuid=self.DomainGuid, 

1465 DnsForestName=self.DnsForestName, 

1466 DnsDomainName=self.DnsForestName, 

1467 DnsHostName=self.DnsHostName, 

1468 NetbiosComputerName=self.NetbiosComputerName, 

1469 DcSiteName=self.DcSiteName, 

1470 ClientSiteName=self.DcSiteName, 

1471 ) 

1472 return resp 

1473 

1474 

1475_located_dc = collections.namedtuple("LocatedDC", ["ip", "samlogon"]) 

1476_dclocatorcache = conf.netcache.new_cache("dclocator", 600) 

1477 

1478 

1479@conf.commands.register 

1480def dclocator( 

1481 realm: str, 

1482 site: str = "", 

1483 qtype: str = "A", 

1484 mode: str = "ldap", 

1485 port: int = None, 

1486 timeout: int = 1, 

1487 NtVersion: int = None, 

1488 debug: int = 0, 

1489): 

1490 """ 

1491 Perform a DC Locator as per [MS-ADTS] sect 6.3.6 or RFC4120. 

1492 

1493 :param realm: the kerberos realm to locate 

1494 :param site: if provided, a Site name 

1495 :param mode: Detect if a server is up and joinable thanks to one of: 

1496 

1497 - 'nocheck': Do not check that servers are online. 

1498 - 'ldap': Use the LDAP ping (CLDAP) per [MS-ADTS]. Default. 

1499 This will however not work with MIT Kerberos servers. 

1500 - 'connect': connect to specified port to test the connection. 

1501 

1502 :param mode: in connect mode, the port to connect to. (e.g. 88) 

1503 :param debug: print debug logs 

1504 

1505 This is cached in conf.netcache.dclocator. 

1506 """ 

1507 if NtVersion is None: 

1508 # Windows' default 

1509 NtVersion = ( 

1510 0x00000002 # V5 

1511 | 0x00000004 # V5EX 

1512 | 0x00000010 # V5EX_WITH_CLOSEST_SITE 

1513 | 0x01000000 # AVOID_NT4EMUL 

1514 | 0x20000000 # IP 

1515 ) 

1516 # Check cache 

1517 cache_ident = ";".join([realm, qtype, mode, str(NtVersion), site]).lower() 

1518 if cache_ident in _dclocatorcache: 

1519 return _dclocatorcache[cache_ident] 

1520 # Perform DNS-Based discovery (6.3.6.1) 

1521 # 1. SRV records 

1522 if site: 

1523 qname = ("_kerberos._tcp.%s._sites.dc._msdcs.%s" % (site, realm)).lower() 

1524 else: 

1525 qname = "_kerberos._tcp.dc._msdcs.%s" % realm.lower() 

1526 if debug: 

1527 log_runtime.info("DC Locator: requesting SRV for '%s' ..." % qname) 

1528 try: 

1529 hosts = [ 

1530 x.target 

1531 for x in dns_resolve( 

1532 qname=qname, 

1533 qtype="SRV", 

1534 timeout=timeout, 

1535 ) 

1536 ] 

1537 except TimeoutError: 

1538 raise TimeoutError("Resolution of %s timed out" % qname) 

1539 if not hosts: 

1540 raise ValueError("No DNS record found for %s" % qname) 

1541 elif debug: 

1542 log_runtime.info( 

1543 "DC Locator: got %s. Resolving %s records ..." % (hosts, qtype) 

1544 ) 

1545 # 2. A records 

1546 ips = [] 

1547 for host in hosts: 

1548 arec = dns_resolve( 

1549 qname=host, 

1550 qtype=qtype, 

1551 timeout=timeout, 

1552 ) 

1553 if arec: 

1554 ips.extend(x.rdata for x in arec) 

1555 if not ips: 

1556 raise ValueError("Could not get any %s records for %s" % (qtype, hosts)) 

1557 elif debug: 

1558 log_runtime.info("DC Locator: got %s . Mode: %s" % (ips, mode)) 

1559 # Pick first online host. We have three options 

1560 if mode == "nocheck": 

1561 # Don't check anything. Not recommended 

1562 return _located_dc(ips[0], None) 

1563 elif mode == "connect": 

1564 assert port is not None, "Must provide a port in connect mode !" 

1565 # Compatibility with MIT Kerberos servers 

1566 for ip in ips: # TODO: "addresses in weighted random order [RFC2782]" 

1567 if debug: 

1568 log_runtime.info("DC Locator: connecting to %s on %s ..." % (ip, port)) 

1569 try: 

1570 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 

1571 sock.settimeout(timeout) 

1572 sock.connect((ip, port)) 

1573 # Success 

1574 result = _located_dc(ip, None) 

1575 # Cache 

1576 _dclocatorcache[cache_ident] = result 

1577 return result 

1578 except OSError: 

1579 # Host timed out, No route to host, etc. 

1580 if debug: 

1581 log_runtime.info("DC Locator: %s timed out." % ip) 

1582 continue 

1583 finally: 

1584 sock.close() 

1585 raise ValueError("No host was reachable on port %s among %s" % (port, ips)) 

1586 elif mode == "ldap": 

1587 # Real 'LDAP Ping' per [MS-ADTS] 

1588 for ip in ips: # TODO: "addresses in weighted random order [RFC2782]" 

1589 if debug: 

1590 log_runtime.info("DC Locator: LDAP Ping %s on ..." % ip) 

1591 try: 

1592 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 

1593 sock.settimeout(timeout) 

1594 sock.connect((ip, 389)) 

1595 sock = SimpleSocket(sock, CLDAP) 

1596 pkt = sock.sr1( 

1597 CLDAP( 

1598 protocolOp=LDAP_SearchRequest( 

1599 filter=LDAP_Filter( 

1600 filter=LDAP_FilterAnd( 

1601 vals=[ 

1602 LDAP_Filter( 

1603 filter=LDAP_FilterEqual( 

1604 attributeType=ASN1_STRING(b"DnsDomain"), 

1605 attributeValue=ASN1_STRING(realm), 

1606 ) 

1607 ), 

1608 LDAP_Filter( 

1609 filter=LDAP_FilterEqual( 

1610 attributeType=ASN1_STRING(b"NtVer"), 

1611 attributeValue=ASN1_STRING( 

1612 struct.pack("<I", NtVersion) 

1613 ), 

1614 ) 

1615 ), 

1616 ] 

1617 ) 

1618 ), 

1619 attributes=[ 

1620 LDAP_SearchRequestAttribute( 

1621 type=ASN1_STRING(b"Netlogon") 

1622 ) 

1623 ], 

1624 ), 

1625 user=None, 

1626 ), 

1627 timeout=timeout, 

1628 verbose=0, 

1629 ) 

1630 if pkt: 

1631 # Check if we have a search response 

1632 response = None 

1633 if isinstance(pkt.protocolOp, LDAP_SearchResponseEntry): 

1634 try: 

1635 response = next( 

1636 NETLOGON(x.values[0].value.val) 

1637 for x in pkt.protocolOp.attributes 

1638 if x.type.val.lower() == b"netlogon" 

1639 ) 

1640 except StopIteration: 

1641 pass 

1642 result = _located_dc(ip, response) 

1643 # Cache 

1644 _dclocatorcache[cache_ident] = result 

1645 return result 

1646 except OSError: 

1647 # Host timed out, No route to host, etc. 

1648 if debug: 

1649 log_runtime.info("DC Locator: %s timed out." % ip) 

1650 continue 

1651 finally: 

1652 sock.close() 

1653 raise ValueError("No LDAP ping succeeded on any of %s. Try another mode?" % ips) 

1654 

1655 

1656##################### 

1657# Basic LDAP client # 

1658##################### 

1659 

1660 

1661class LDAP_BIND_MECHS(StrEnum): 

1662 NONE = "ANONYMOUS" 

1663 SIMPLE = "SIMPLE" 

1664 SASL_GSSAPI = "GSSAPI" 

1665 SASL_GSS_SPNEGO = "GSS-SPNEGO" 

1666 SASL_EXTERNAL = "EXTERNAL" 

1667 SASL_DIGEST_MD5 = "DIGEST-MD5" 

1668 # [MS-ADTS] extension 

1669 SICILY = "SICILY" 

1670 

1671 

1672class LDAP_SASL_GSSAPI_SsfCap(Packet): 

1673 """ 

1674 RFC2222 sect 7.2.1 and 7.2.2 negotiate token 

1675 """ 

1676 

1677 fields_desc = [ 

1678 FlagsField( 

1679 "supported_security_layers", 

1680 0, 

1681 -8, 

1682 { 

1683 # https://github.com/cyrusimap/cyrus-sasl/blob/7e2feaeeb2e37d38cb5fa957d0e8a599ced22612/plugins/gssapi.c#L221 

1684 0x01: "NONE", 

1685 0x02: "INTEGRITY", 

1686 0x04: "CONFIDENTIALITY", 

1687 }, 

1688 ), 

1689 ThreeBytesField("max_output_token_size", 0), 

1690 ] 

1691 

1692 

1693class LDAP_SASL_Buffer(Packet): 

1694 """ 

1695 RFC 4422 sect 3.7 

1696 """ 

1697 

1698 # "Each buffer of protected data is transferred over the underlying 

1699 # transport connection as a sequence of octets prepended with a four- 

1700 # octet field in network byte order that represents the length of the 

1701 # buffer." 

1702 

1703 fields_desc = [ 

1704 FieldLenField("BufferLength", None, fmt="!I", length_of="Buffer"), 

1705 _GSSAPI_Field("Buffer", LDAP), 

1706 ] 

1707 

1708 def hashret(self): 

1709 return b"ldap" 

1710 

1711 def answers(self, other): 

1712 return isinstance(other, LDAP_SASL_Buffer) 

1713 

1714 @classmethod 

1715 def tcp_reassemble(cls, data, *args, **kwargs): 

1716 if len(data) < 4: 

1717 return None 

1718 if data[0] == 0x30: 

1719 # Add a heuristic to detect LDAP errors 

1720 try: 

1721 xlen, x = BER_len_dec(BER_id_dec(data)[1]) 

1722 except (BER_Decoding_Error, IndexError): 

1723 return None 

1724 if xlen and xlen == len(x): 

1725 return LDAP(data) 

1726 # Check BufferLength 

1727 length = struct.unpack("!I", data[:4])[0] + 4 

1728 if len(data) >= length: 

1729 return cls(data) 

1730 

1731 

1732class LDAP_Exception(RuntimeError): 

1733 __slots__ = ["resultCode", "diagnosticMessage"] 

1734 

1735 def __init__(self, *args, **kwargs): 

1736 resp = kwargs.pop("resp", None) 

1737 if resp: 

1738 self.resultCode = resp.protocolOp.sprintf("%resultCode%") 

1739 self.diagnosticMessage = resp.protocolOp.diagnosticMessage.val.rstrip( 

1740 b"\x00" 

1741 ).decode(errors="backslashreplace") 

1742 else: 

1743 self.resultCode = kwargs.pop("resultCode", None) 

1744 self.diagnosticMessage = kwargs.pop("diagnosticMessage", None) 

1745 super(LDAP_Exception, self).__init__(*args, **kwargs) 

1746 # If there's a 'data' string argument, attempt to parse the error code. 

1747 try: 

1748 m = re.match(r"(\d+): LdapErr.*", self.diagnosticMessage) 

1749 if m: 

1750 errstr = m.group(1) 

1751 err = int(errstr, 16) 

1752 if err in STATUS_ERREF: 

1753 self.diagnosticMessage = self.diagnosticMessage.replace( 

1754 errstr, errstr + " (%s)" % STATUS_ERREF[err], 1 

1755 ) 

1756 except ValueError: 

1757 pass 

1758 # Add note if this exception is raised 

1759 self.add_note(self.diagnosticMessage) 

1760 

1761 

1762class LDAP_Client(object): 

1763 """ 

1764 A basic LDAP client 

1765 

1766 The complete documentation is available at 

1767 https://scapy.readthedocs.io/en/latest/layers/ldap.html 

1768 

1769 Example 1 - SICILY - NTLM (with encryption):: 

1770 

1771 client = LDAP_Client() 

1772 client.connect("192.168.0.100") 

1773 ssp = NTLMSSP(UPN="Administrator", PASSWORD="Password1!") 

1774 client.bind( 

1775 LDAP_BIND_MECHS.SICILY, 

1776 ssp=ssp, 

1777 encrypt=True, 

1778 ) 

1779 

1780 Example 2 - SASL_GSSAPI - Kerberos (with signing):: 

1781 

1782 client = LDAP_Client() 

1783 client.connect("192.168.0.100") 

1784 ssp = KerberosSSP(UPN="Administrator@domain.local", PASSWORD="Password1!", 

1785 SPN="ldap/dc1.domain.local") 

1786 client.bind( 

1787 LDAP_BIND_MECHS.SASL_GSSAPI, 

1788 ssp=ssp, 

1789 sign=True, 

1790 ) 

1791 

1792 Example 3 - SASL_GSS_SPNEGO - NTLM / Kerberos:: 

1793 

1794 client = LDAP_Client() 

1795 client.connect("192.168.0.100") 

1796 ssp = SPNEGOSSP([ 

1797 NTLMSSP(UPN="Administrator", PASSWORD="Password1!"), 

1798 KerberosSSP(UPN="Administrator@domain.local", PASSWORD="Password1!", 

1799 SPN="ldap/dc1.domain.local"), 

1800 ]) 

1801 client.bind( 

1802 LDAP_BIND_MECHS.SASL_GSS_SPNEGO, 

1803 ssp=ssp, 

1804 ) 

1805 

1806 Example 4 - Simple bind over TLS:: 

1807 

1808 client = LDAP_Client() 

1809 client.connect("192.168.0.100", use_ssl=True) 

1810 client.bind( 

1811 LDAP_BIND_MECHS.SIMPLE, 

1812 simple_username="Administrator", 

1813 simple_password="Password1!", 

1814 ) 

1815 """ 

1816 

1817 def __init__( 

1818 self, 

1819 verb=True, 

1820 ): 

1821 self.sock = None 

1822 self.host = None 

1823 self.verb = verb 

1824 self.ssl = False 

1825 self.sslcontext = None 

1826 self.ssp = None 

1827 self.sspcontext = None 

1828 self.encrypt = False 

1829 self.sign = False 

1830 # Session status 

1831 self.sasl_wrap = False 

1832 self.chan_bindings = GSS_C_NO_CHANNEL_BINDINGS 

1833 self.bound = False 

1834 self.messageID = 0 

1835 

1836 def connect( 

1837 self, 

1838 host, 

1839 port=None, 

1840 use_ssl=False, 

1841 sslcontext=None, 

1842 sni=None, 

1843 no_check_certificate=False, 

1844 timeout=5, 

1845 ): 

1846 """ 

1847 Initiate a connection 

1848 

1849 :param host: the IP or hostname to connect to. 

1850 :param port: the port to connect to. (Default: 389 or 636) 

1851 

1852 :param use_ssl: whether to use LDAPS or not. (Default: False) 

1853 :param sslcontext: an optional SSLContext to use. 

1854 :param sni: (optional) specify the SNI to use if LDAPS, otherwise use ip. 

1855 :param no_check_certificate: with SSL, do not check the certificate 

1856 """ 

1857 self.ssl = use_ssl 

1858 self.sslcontext = sslcontext 

1859 self.timeout = timeout 

1860 self.host = host 

1861 

1862 if port is None: 

1863 if self.ssl: 

1864 port = 636 

1865 else: 

1866 port = 389 

1867 

1868 # Create and configure socket 

1869 sock = socket.socket() 

1870 sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) 

1871 sock.settimeout(timeout) 

1872 

1873 # Connect 

1874 if self.verb: 

1875 print( 

1876 "\u2503 Connecting to %s on port %s%s..." 

1877 % ( 

1878 host, 

1879 port, 

1880 " with SSL" if self.ssl else "", 

1881 ) 

1882 ) 

1883 sock.connect((host, port)) 

1884 if self.verb: 

1885 print( 

1886 conf.color_theme.green( 

1887 "\u2514 Connected from %s" % repr(sock.getsockname()) 

1888 ) 

1889 ) 

1890 

1891 # For SSL, build and apply SSLContext 

1892 if self.ssl: 

1893 if self.sslcontext is None: 

1894 if no_check_certificate: 

1895 context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) 

1896 context.check_hostname = False 

1897 context.verify_mode = ssl.CERT_NONE 

1898 else: 

1899 context = ssl.create_default_context() 

1900 else: 

1901 context = self.sslcontext 

1902 sock = context.wrap_socket(sock, server_hostname=sni or host) 

1903 

1904 # Wrap the socket in a Scapy socket 

1905 if self.ssl: 

1906 # Compute the channel binding token (CBT) 

1907 self.chan_bindings = GssChannelBindings.fromssl( 

1908 ChannelBindingType.TLS_SERVER_END_POINT, 

1909 sslsock=sock, 

1910 ) 

1911 

1912 self.sock = SSLStreamSocket(sock, LDAP) 

1913 else: 

1914 self.sock = StreamSocket(sock, LDAP) 

1915 

1916 def sr1(self, protocolOp, controls: List[LDAP_Control] = None, **kwargs): 

1917 self.messageID += 1 

1918 if self.verb: 

1919 print(conf.color_theme.opening(">> %s" % protocolOp.__class__.__name__)) 

1920 

1921 # Build packet 

1922 pkt = LDAP( 

1923 messageID=self.messageID, 

1924 protocolOp=protocolOp, 

1925 Controls=controls, 

1926 ) 

1927 

1928 # If signing / encryption is used, apply 

1929 if self.sasl_wrap: 

1930 pkt = LDAP_SASL_Buffer( 

1931 Buffer=self.ssp.GSS_Wrap( 

1932 self.sspcontext, 

1933 bytes(pkt), 

1934 conf_req_flag=self.encrypt, 

1935 # LDAP on Windows doesn't use SECBUFFER_PADDING, which 

1936 # isn't supported by GSS_WrapEx. We add our own flag to 

1937 # tell it. 

1938 qop_req=GSS_QOP_REQ_FLAGS.GSS_S_NO_SECBUFFER_PADDING, 

1939 ) 

1940 ) 

1941 

1942 # Send / Receive 

1943 resp = self.sock.sr1( 

1944 pkt, 

1945 verbose=0, 

1946 **kwargs, 

1947 ) 

1948 # Check for unsolicited notification 

1949 if resp and LDAP in resp and resp[LDAP].unsolicited: 

1950 if self.verb: 

1951 resp.show() 

1952 print(conf.color_theme.fail("! Got unsolicited notification.")) 

1953 return resp 

1954 

1955 # If signing / encryption is used, unpack 

1956 if self.sasl_wrap: 

1957 if resp.Buffer: 

1958 resp = LDAP( 

1959 self.ssp.GSS_Unwrap( 

1960 self.sspcontext, 

1961 resp.Buffer, 

1962 ) 

1963 ) 

1964 if not resp.unsolicited and resp.messageID != self.messageID: 

1965 raise ValueError( 

1966 "LDAP response message ID does not match request !" 

1967 ) 

1968 else: 

1969 resp = None 

1970 

1971 # Verbose display 

1972 if self.verb: 

1973 if not resp: 

1974 print(conf.color_theme.fail("! Bad response.")) 

1975 return 

1976 else: 

1977 print( 

1978 conf.color_theme.success( 

1979 "<< %s" 

1980 % ( 

1981 resp.protocolOp.__class__.__name__ 

1982 if LDAP in resp 

1983 else resp.__class__.__name__ 

1984 ) 

1985 ) 

1986 ) 

1987 return resp 

1988 

1989 def bind( 

1990 self, 

1991 mech, 

1992 ssp=None, 

1993 sign: Optional[bool] = None, 

1994 encrypt: Optional[bool] = None, 

1995 simple_username=None, 

1996 simple_password=None, 

1997 ): 

1998 """ 

1999 Send Bind request. 

2000 

2001 :param mech: one of LDAP_BIND_MECHS 

2002 :param ssp: the SSP object to use for binding 

2003 

2004 :param sign: request signing when binding 

2005 :param encrypt: request encryption when binding 

2006 

2007 : 

2008 This acts differently based on the :mech: provided during initialization. 

2009 """ 

2010 # Bind default values: if NTLM then encrypt, else sign unless anonymous/simple 

2011 if encrypt is None: 

2012 encrypt = mech == LDAP_BIND_MECHS.SICILY 

2013 if sign is None and not encrypt: 

2014 sign = mech not in [LDAP_BIND_MECHS.NONE, LDAP_BIND_MECHS.SIMPLE] 

2015 

2016 # Store and check consistency 

2017 self.mech = mech 

2018 self.ssp = ssp # type: SSP 

2019 self.sign = sign 

2020 self.encrypt = encrypt 

2021 self.sspcontext = None 

2022 

2023 if mech is None or not isinstance(mech, LDAP_BIND_MECHS): 

2024 raise ValueError( 

2025 "'mech' attribute is required and must be one of LDAP_BIND_MECHS." 

2026 ) 

2027 

2028 if mech == LDAP_BIND_MECHS.SASL_GSSAPI: 

2029 from scapy.layers.kerberos import KerberosSSP 

2030 

2031 if not isinstance(self.ssp, KerberosSSP): 

2032 raise ValueError("Only raw KerberosSSP is supported with SASL_GSSAPI !") 

2033 elif mech == LDAP_BIND_MECHS.SASL_GSS_SPNEGO: 

2034 from scapy.layers.spnego import SPNEGOSSP 

2035 

2036 if not isinstance(self.ssp, SPNEGOSSP): 

2037 if WINDOWS: 

2038 from scapy.arch.windows.sspi import WinSSP 

2039 

2040 if not isinstance(self.ssp, WinSSP): 

2041 raise ValueError( 

2042 "Only SPNEGOSSP is supported with SASL_GSS_SPNEGO !" 

2043 ) 

2044 else: 

2045 raise ValueError( 

2046 "Only SPNEGOSSP is supported with SASL_GSS_SPNEGO !" 

2047 ) 

2048 elif mech == LDAP_BIND_MECHS.SICILY: 

2049 from scapy.layers.ntlm import NTLMSSP 

2050 

2051 if not isinstance(self.ssp, NTLMSSP): 

2052 raise ValueError("Only raw NTLMSSP is supported with SICILY !") 

2053 if self.sign and not self.encrypt: 

2054 raise ValueError( 

2055 "NTLM on LDAP does not support signing without encryption !" 

2056 ) 

2057 elif mech in [LDAP_BIND_MECHS.NONE, LDAP_BIND_MECHS.SIMPLE]: 

2058 if self.sign or self.encrypt: 

2059 raise ValueError("Cannot use 'sign' or 'encrypt' with NONE or SIMPLE !") 

2060 else: 

2061 raise ValueError("Mech %s is still unimplemented !" % mech) 

2062 

2063 if self.ssp is not None and mech in [ 

2064 LDAP_BIND_MECHS.NONE, 

2065 LDAP_BIND_MECHS.SIMPLE, 

2066 ]: 

2067 raise ValueError("%s cannot be used with a ssp !" % mech.value) 

2068 

2069 # Now perform the bind, depending on the mech 

2070 if self.mech == LDAP_BIND_MECHS.SIMPLE: 

2071 # Simple binding 

2072 resp = self.sr1( 

2073 LDAP_BindRequest( 

2074 bind_name=ASN1_STRING(simple_username or ""), 

2075 authentication=LDAP_Authentication_simple( 

2076 simple_password or "", 

2077 ), 

2078 ) 

2079 ) 

2080 if ( 

2081 LDAP not in resp 

2082 or not isinstance(resp.protocolOp, LDAP_BindResponse) 

2083 or resp.protocolOp.resultCode != 0 

2084 ): 

2085 raise LDAP_Exception( 

2086 "LDAP simple bind failed !", 

2087 resp=resp, 

2088 ) 

2089 status = GSS_S_COMPLETE 

2090 elif self.mech == LDAP_BIND_MECHS.SICILY: 

2091 # [MS-ADTS] sect 5.1.1.1.3 

2092 # 1. Package Discovery 

2093 resp = self.sr1( 

2094 LDAP_BindRequest( 

2095 bind_name=ASN1_STRING(b""), 

2096 authentication=LDAP_Authentication_sicilyPackageDiscovery(b""), 

2097 ) 

2098 ) 

2099 if resp.protocolOp.resultCode != 0: 

2100 raise LDAP_Exception( 

2101 "Sicily package discovery failed !", 

2102 resp=resp, 

2103 ) 

2104 # 2. First exchange: Negotiate 

2105 self.sspcontext, token, status = self.ssp.GSS_Init_sec_context( 

2106 self.sspcontext, 

2107 target_name="ldap/" + self.host, 

2108 req_flags=( 

2109 GSS_C_FLAGS.GSS_C_REPLAY_FLAG 

2110 | GSS_C_FLAGS.GSS_C_SEQUENCE_FLAG 

2111 | GSS_C_FLAGS.GSS_C_MUTUAL_FLAG 

2112 | (GSS_C_FLAGS.GSS_C_INTEG_FLAG if self.sign else 0) 

2113 | (GSS_C_FLAGS.GSS_C_CONF_FLAG if self.encrypt else 0) 

2114 ), 

2115 ) 

2116 resp = self.sr1( 

2117 LDAP_BindRequest( 

2118 bind_name=ASN1_STRING(b"NTLM"), 

2119 authentication=LDAP_Authentication_sicilyNegotiate( 

2120 bytes(token), 

2121 ), 

2122 ) 

2123 ) 

2124 val = resp.protocolOp.serverCreds 

2125 if not val: 

2126 raise LDAP_Exception( 

2127 "Sicily negotiate failed !", 

2128 resp=resp, 

2129 ) 

2130 # 3. Second exchange: Response 

2131 self.sspcontext, token, status = self.ssp.GSS_Init_sec_context( 

2132 self.sspcontext, 

2133 input_token=GSSAPI_BLOB(val), 

2134 target_name="ldap/" + self.host, 

2135 chan_bindings=self.chan_bindings, 

2136 ) 

2137 resp = self.sr1( 

2138 LDAP_BindRequest( 

2139 bind_name=ASN1_STRING(b"NTLM"), 

2140 authentication=LDAP_Authentication_sicilyResponse( 

2141 bytes(token), 

2142 ), 

2143 ) 

2144 ) 

2145 if resp.protocolOp.resultCode != 0: 

2146 raise LDAP_Exception( 

2147 "Sicily response failed !", 

2148 resp=resp, 

2149 ) 

2150 elif self.mech in [ 

2151 LDAP_BIND_MECHS.SASL_GSS_SPNEGO, 

2152 LDAP_BIND_MECHS.SASL_GSSAPI, 

2153 ]: 

2154 # GSSAPI or SPNEGO 

2155 self.sspcontext, token, status = self.ssp.GSS_Init_sec_context( 

2156 self.sspcontext, 

2157 target_name="ldap/" + self.host, 

2158 req_flags=( 

2159 # Required flags for GSSAPI: RFC4752 sect 3.1 

2160 GSS_C_FLAGS.GSS_C_REPLAY_FLAG 

2161 | GSS_C_FLAGS.GSS_C_SEQUENCE_FLAG 

2162 | GSS_C_FLAGS.GSS_C_MUTUAL_FLAG 

2163 | (GSS_C_FLAGS.GSS_C_INTEG_FLAG if self.sign else 0) 

2164 | (GSS_C_FLAGS.GSS_C_CONF_FLAG if self.encrypt else 0) 

2165 ), 

2166 chan_bindings=self.chan_bindings, 

2167 ) 

2168 if status not in [GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED]: 

2169 raise RuntimeError( 

2170 "%s: GSS_Init_sec_context failed with %s !" 

2171 % (self.mech.name, repr(status)), 

2172 ) 

2173 while token: 

2174 resp = self.sr1( 

2175 LDAP_BindRequest( 

2176 bind_name=ASN1_STRING(b""), 

2177 authentication=LDAP_Authentication_SaslCredentials( 

2178 mechanism=ASN1_STRING(self.mech.value), 

2179 credentials=ASN1_STRING(bytes(token)), 

2180 ), 

2181 ) 

2182 ) 

2183 if not isinstance(resp.protocolOp, LDAP_BindResponse): 

2184 raise LDAP_Exception( 

2185 "%s bind failed !" % self.mech.name, 

2186 resp=resp, 

2187 ) 

2188 val = resp.protocolOp.serverSaslCredsData 

2189 if resp.protocolOp.resultCode not in [0, 14]: 

2190 raise LDAP_Exception( 

2191 "SASL authentication failed !", 

2192 resp=resp, 

2193 ) 

2194 self.sspcontext, token, status = self.ssp.GSS_Init_sec_context( 

2195 self.sspcontext, 

2196 input_token=GSSAPI_BLOB(val), 

2197 target_name="ldap/" + self.host, 

2198 chan_bindings=self.chan_bindings, 

2199 ) 

2200 else: 

2201 status = GSS_S_COMPLETE 

2202 if status != GSS_S_COMPLETE: 

2203 raise RuntimeError( 

2204 "%s: GSS_Init_sec_context failed with %s !" 

2205 % (self.mech.name, repr(status)), 

2206 ) 

2207 elif self.mech == LDAP_BIND_MECHS.SASL_GSSAPI: 

2208 # GSSAPI has 2 extra exchanges 

2209 # https://datatracker.ietf.org/doc/html/rfc2222#section-7.2.1 

2210 resp = self.sr1( 

2211 LDAP_BindRequest( 

2212 bind_name=ASN1_STRING(b""), 

2213 authentication=LDAP_Authentication_SaslCredentials( 

2214 mechanism=ASN1_STRING(self.mech.value), 

2215 credentials=None, 

2216 ), 

2217 ) 

2218 ) 

2219 # Parse server-supported layers 

2220 saslOptions = LDAP_SASL_GSSAPI_SsfCap( 

2221 self.ssp.GSS_Unwrap( 

2222 self.sspcontext, 

2223 GSSAPI_BLOB_SIGNATURE(resp.protocolOp.serverSaslCredsData), 

2224 ) 

2225 ) 

2226 if self.sign and not saslOptions.supported_security_layers.INTEGRITY: 

2227 raise RuntimeError("GSSAPI SASL failed to negotiate INTEGRITY !") 

2228 if ( 

2229 self.encrypt 

2230 and not saslOptions.supported_security_layers.CONFIDENTIALITY 

2231 ): 

2232 raise RuntimeError("GSSAPI SASL failed to negotiate CONFIDENTIALITY !") 

2233 # Announce client-supported layers 

2234 saslOptions = LDAP_SASL_GSSAPI_SsfCap( 

2235 supported_security_layers=( 

2236 "+".join( 

2237 (["INTEGRITY"] if self.sign else []) 

2238 + (["CONFIDENTIALITY"] if self.encrypt else []) 

2239 ) 

2240 if (self.sign or self.encrypt) 

2241 else "NONE" 

2242 ), 

2243 # Same as server 

2244 max_output_token_size=saslOptions.max_output_token_size, 

2245 ) 

2246 resp = self.sr1( 

2247 LDAP_BindRequest( 

2248 bind_name=ASN1_STRING(b""), 

2249 authentication=LDAP_Authentication_SaslCredentials( 

2250 mechanism=ASN1_STRING(self.mech.value), 

2251 credentials=self.ssp.GSS_Wrap( 

2252 self.sspcontext, 

2253 bytes(saslOptions), 

2254 # We still haven't finished negotiating 

2255 conf_req_flag=False, 

2256 ), 

2257 ), 

2258 ) 

2259 ) 

2260 if resp.protocolOp.resultCode != 0: 

2261 raise LDAP_Exception( 

2262 "GSSAPI SASL failed to negotiate client security flags !", 

2263 resp=resp, 

2264 ) 

2265 

2266 # If we use SPNEGO and NTLMSSP was used, understand we can't use sign 

2267 if self.mech == LDAP_BIND_MECHS.SASL_GSS_SPNEGO: 

2268 from scapy.layers.ntlm import NTLMSSP 

2269 

2270 if isinstance(self.sspcontext.ssp, NTLMSSP): 

2271 if self.sign and not self.encrypt: 

2272 raise ValueError( 

2273 "NTLM with GSS-SPNEGO cannot sign without encryption !" 

2274 ) 

2275 self.sign = False 

2276 

2277 # SASL wrapping is now available. 

2278 self.sasl_wrap = self.encrypt or self.sign 

2279 if self.sasl_wrap: 

2280 self.sock.closed = True # prevent closing by marking it as already closed. 

2281 self.sock = StreamSocket(self.sock.ins, LDAP_SASL_Buffer) 

2282 

2283 # Success. 

2284 if self.verb: 

2285 print("%s bind succeeded !" % self.mech.name) 

2286 self.bound = True 

2287 

2288 _TEXT_REG = re.compile(b"^[%s]*$" % re.escape(string.printable.encode())) 

2289 

2290 def search( 

2291 self, 

2292 baseObject: str = "", 

2293 filter: str = "", 

2294 scope=0, 

2295 derefAliases=0, 

2296 sizeLimit=300000, 

2297 timeLimit=3000, 

2298 attrsOnly=0, 

2299 attributes: List[str] = [], 

2300 controls: List[LDAP_Control] = [], 

2301 ) -> Dict[str, List[Any]]: 

2302 """ 

2303 Perform a LDAP search. 

2304 

2305 :param baseObject: the dn of the base object to search in. 

2306 :param filter: the filter to apply to the search (currently unsupported) 

2307 :param scope: 0=baseObject, 1=singleLevel, 2=wholeSubtree 

2308 """ 

2309 if baseObject == "rootDSE": 

2310 baseObject = "" 

2311 if filter: 

2312 filter = LDAP_Filter.from_rfc2254_string(filter) 

2313 else: 

2314 # Default filter: (objectClass=*) 

2315 filter = LDAP_Filter( 

2316 filter=LDAP_FilterPresent( 

2317 present=ASN1_STRING(b"objectClass"), 

2318 ) 

2319 ) 

2320 # we loop as we might need more than one packet thanks to paging 

2321 cookie = b"" 

2322 entries = {} 

2323 while True: 

2324 resp = self.sr1( 

2325 LDAP_SearchRequest( 

2326 filter=filter, 

2327 attributes=[ 

2328 LDAP_SearchRequestAttribute(type=ASN1_STRING(attr)) 

2329 for attr in attributes 

2330 ], 

2331 baseObject=ASN1_STRING(baseObject), 

2332 scope=ASN1_ENUMERATED(scope), 

2333 derefAliases=ASN1_ENUMERATED(derefAliases), 

2334 sizeLimit=ASN1_INTEGER(sizeLimit), 

2335 timeLimit=ASN1_INTEGER(timeLimit), 

2336 attrsOnly=ASN1_BOOLEAN(attrsOnly), 

2337 ), 

2338 controls=( 

2339 controls 

2340 + ( 

2341 [ 

2342 # This control is only usable when bound. 

2343 LDAP_Control( 

2344 controlType="1.2.840.113556.1.4.319", 

2345 criticality=True, 

2346 controlValue=LDAP_realSearchControlValue( 

2347 size=100, # paging to 100 per 100 

2348 cookie=cookie, 

2349 ), 

2350 ) 

2351 ] 

2352 if self.bound 

2353 else [] 

2354 ) 

2355 ), 

2356 timeout=self.timeout, 

2357 ) 

2358 if LDAP_SearchResponseResultDone not in resp: 

2359 resp.show() 

2360 raise TimeoutError("Search timed out.") 

2361 # Now, reassemble the results 

2362 

2363 def _s(x): 

2364 try: 

2365 return x.decode() 

2366 except UnicodeDecodeError: 

2367 return x 

2368 

2369 def _ssafe(x): 

2370 if self._TEXT_REG.match(x): 

2371 return x.decode() 

2372 else: 

2373 return x 

2374 

2375 # For each individual packet response 

2376 while resp: 

2377 # Find all 'LDAP' layers 

2378 if LDAP not in resp: 

2379 log_runtime.warning("Invalid response: %s", repr(resp)) 

2380 break 

2381 if LDAP_SearchResponseEntry in resp.protocolOp: 

2382 attrs = { 

2383 _s(attr.type.val): [_ssafe(x.value.val) for x in attr.values] 

2384 for attr in resp.protocolOp.attributes 

2385 } 

2386 entries[_s(resp.protocolOp.objectName.val)] = attrs 

2387 elif LDAP_SearchResponseResultDone in resp.protocolOp: 

2388 resultCode = resp.protocolOp.resultCode 

2389 if resultCode != 0x0: # != success 

2390 log_runtime.warning( 

2391 resp.protocolOp.sprintf("Got response: %resultCode%") 

2392 ) 

2393 raise LDAP_Exception( 

2394 "LDAP search failed !", 

2395 resp=resp, 

2396 ) 

2397 else: 

2398 # success 

2399 if resp.Controls: 

2400 # We have controls back 

2401 realSearchControlValue = next( 

2402 ( 

2403 c.controlValue 

2404 for c in resp.Controls 

2405 if isinstance( 

2406 c.controlValue, LDAP_realSearchControlValue 

2407 ) 

2408 ), 

2409 None, 

2410 ) 

2411 if realSearchControlValue is not None: 

2412 # has paging ! 

2413 cookie = realSearchControlValue.cookie.val 

2414 break 

2415 break 

2416 resp = resp.payload 

2417 # If we have a cookie, continue 

2418 if not cookie: 

2419 break 

2420 return entries 

2421 

2422 def modify( 

2423 self, 

2424 object: str, 

2425 changes: List[LDAP_ModifyRequestChange], 

2426 controls: List[LDAP_Control] = [], 

2427 ) -> None: 

2428 """ 

2429 Perform a LDAP modify request. 

2430 

2431 :returns: 

2432 """ 

2433 resp = self.sr1( 

2434 LDAP_ModifyRequest( 

2435 object=object, 

2436 changes=changes, 

2437 ), 

2438 controls=controls, 

2439 timeout=self.timeout, 

2440 ) 

2441 if ( 

2442 LDAP_ModifyResponse not in resp.protocolOp 

2443 or resp.protocolOp.resultCode != 0 

2444 ): 

2445 raise LDAP_Exception( 

2446 "LDAP modify failed !", 

2447 resp=resp, 

2448 ) 

2449 

2450 def add( 

2451 self, 

2452 entry: str, 

2453 attributes: Union[Dict[str, List[Any]], List[ASN1_Packet]], 

2454 controls: List[LDAP_Control] = [], 

2455 ): 

2456 """ 

2457 Perform a LDAP add request. 

2458 

2459 :param attributes: the attributes to add. We support two formats: 

2460 - a list of LDAP_Attribute (or LDAP_PartialAttribute) 

2461 - a dict following {attribute: [list of values]} 

2462 

2463 :returns: 

2464 """ 

2465 # We handle the two cases in the type of attributes 

2466 if isinstance(attributes, dict): 

2467 attributes = [ 

2468 LDAP_Attribute( 

2469 type=ASN1_STRING(k), 

2470 values=[ 

2471 LDAP_AttributeValue( 

2472 value=ASN1_STRING(x), 

2473 ) 

2474 for x in v 

2475 ], 

2476 ) 

2477 for k, v in attributes.items() 

2478 ] 

2479 

2480 resp = self.sr1( 

2481 LDAP_AddRequest( 

2482 entry=ASN1_STRING(entry), 

2483 attributes=attributes, 

2484 ), 

2485 controls=controls, 

2486 timeout=self.timeout, 

2487 ) 

2488 if LDAP_AddResponse not in resp.protocolOp or resp.protocolOp.resultCode != 0: 

2489 raise LDAP_Exception( 

2490 "LDAP add failed !", 

2491 resp=resp, 

2492 ) 

2493 

2494 def modifydn( 

2495 self, 

2496 entry: str, 

2497 newdn: str, 

2498 deleteoldrdn=True, 

2499 controls: List[LDAP_Control] = [], 

2500 ): 

2501 """ 

2502 Perform a LDAP modify DN request. 

2503 

2504 ..note:: This functions calculates the relative DN and superior required for 

2505 LDAP ModifyDN automatically. 

2506 

2507 :param entry: the DN of the entry to rename. 

2508 :param newdn: the new FULL DN of the entry. 

2509 :returns: 

2510 """ 

2511 # RFC4511 sect 4.9 

2512 # Calculate the newrdn (relative DN) and superior 

2513 newrdn, newSuperior = newdn.split(",", 1) 

2514 _, cur_superior = entry.split(",", 1) 

2515 # If the superior hasn't changed, don't update it. 

2516 if cur_superior == newSuperior: 

2517 newSuperior = None 

2518 # Send the request 

2519 resp = self.sr1( 

2520 LDAP_ModifyDNRequest( 

2521 entry=entry, 

2522 newrdn=newrdn, 

2523 newSuperior=newSuperior, 

2524 deleteoldrdn=deleteoldrdn, 

2525 ), 

2526 controls=controls, 

2527 timeout=self.timeout, 

2528 ) 

2529 if ( 

2530 LDAP_ModifyDNResponse not in resp.protocolOp 

2531 or resp.protocolOp.resultCode != 0 

2532 ): 

2533 raise LDAP_Exception( 

2534 "LDAP modify failed !", 

2535 resp=resp, 

2536 ) 

2537 

2538 def close(self): 

2539 if self.verb: 

2540 print("X Connection closed\n") 

2541 self.sock.close() 

2542 self.bound = False 

2543 self.sspcontext = None