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
17import collections
18import ssl
19import socket
20import struct
21import uuid
22
23from enum import Enum
24
25from scapy.arch import get_if_addr
26from scapy.ansmachine import AnsweringMachine
27from scapy.asn1.asn1 import (
28 ASN1_STRING,
29 ASN1_Class,
30 ASN1_Codecs,
31)
32from scapy.asn1.ber import BERcodec_STRING
33from scapy.asn1fields import (
34 ASN1F_BOOLEAN,
35 ASN1F_CHOICE,
36 ASN1F_ENUMERATED,
37 ASN1F_INTEGER,
38 ASN1F_NULL,
39 ASN1F_PACKET,
40 ASN1F_SEQUENCE,
41 ASN1F_SEQUENCE_OF,
42 ASN1F_SET_OF,
43 ASN1F_STRING,
44 ASN1F_optional,
45)
46from scapy.asn1packet import ASN1_Packet
47from scapy.config import conf
48from scapy.error import log_runtime
49from scapy.fields import (
50 FlagsField,
51 ThreeBytesField,
52)
53from scapy.packet import (
54 Packet,
55 bind_bottom_up,
56 bind_layers,
57)
58from scapy.sendrecv import send
59from scapy.supersocket import (
60 SimpleSocket,
61 StreamSocket,
62)
63
64from scapy.layers.dns import dns_resolve
65from scapy.layers.inet import IP, TCP, UDP
66from scapy.layers.inet6 import IPv6
67from scapy.layers.gssapi import (
68 GSS_C_FLAGS,
69 GSS_S_COMPLETE,
70 GSSAPI_BLOB,
71 GSSAPI_BLOB_SIGNATURE,
72 SSP,
73)
74from scapy.layers.kerberos import (
75 _ASN1FString_PacketField,
76)
77from scapy.layers.netbios import NBTDatagram
78from scapy.layers.smb import (
79 NETLOGON,
80 NETLOGON_SAM_LOGON_RESPONSE_EX,
81)
82
83
84# Elements of protocol
85# https://datatracker.ietf.org/doc/html/rfc1777#section-4
86
87LDAPString = ASN1F_STRING
88LDAPOID = ASN1F_STRING
89LDAPDN = LDAPString
90RelativeLDAPDN = LDAPString
91AttributeType = LDAPString
92AttributeValue = ASN1F_STRING
93URI = LDAPString
94
95
96class AttributeValueAssertion(ASN1_Packet):
97 ASN1_codec = ASN1_Codecs.BER
98 ASN1_root = ASN1F_SEQUENCE(
99 AttributeType("attributeType", "organizationName"),
100 AttributeValue("attributeValue", ""),
101 )
102
103
104class LDAPReferral(ASN1_Packet):
105 ASN1_codec = ASN1_Codecs.BER
106 ASN1_root = LDAPString("uri", "")
107
108
109LDAPResult = (
110 ASN1F_ENUMERATED(
111 "resultCode",
112 0,
113 {
114 0: "success",
115 1: "operationsError",
116 2: "protocolError",
117 3: "timeLimitExceeded",
118 4: "sizeLimitExceeded",
119 5: "compareFalse",
120 6: "compareTrue",
121 7: "authMethodNotSupported",
122 8: "strongAuthRequired",
123 10: "referral",
124 11: "adminLimitExceeded",
125 14: "saslBindInProgress",
126 16: "noSuchAttribute",
127 17: "undefinedAttributeType",
128 18: "inappropriateMatching",
129 19: "constraintViolation",
130 20: "attributeOrValueExists",
131 21: "invalidAttributeSyntax",
132 32: "noSuchObject",
133 33: "aliasProblem",
134 34: "invalidDNSyntax",
135 35: "isLeaf",
136 36: "aliasDereferencingProblem",
137 48: "inappropriateAuthentication",
138 49: "invalidCredentials",
139 50: "insufficientAccessRights",
140 51: "busy",
141 52: "unavailable",
142 53: "unwillingToPerform",
143 54: "loopDetect",
144 64: "namingViolation",
145 65: "objectClassViolation",
146 66: "notAllowedOnNonLeaf",
147 67: "notAllowedOnRDN",
148 68: "entryAlreadyExists",
149 69: "objectClassModsProhibited",
150 70: "resultsTooLarge", # CLDAP
151 80: "other",
152 },
153 ),
154 LDAPDN("matchedDN", ""),
155 LDAPString("diagnosticMessage", ""),
156 # LDAP v3 only
157 ASN1F_optional(ASN1F_SEQUENCE_OF("referral", [], LDAPReferral, implicit_tag=0xA3)),
158)
159
160
161# ldap APPLICATION
162
163
164class ASN1_Class_LDAP(ASN1_Class):
165 name = "LDAP"
166 # APPLICATION + CONSTRUCTED = 0x40 | 0x20
167 BindRequest = 0x60
168 BindResponse = 0x61
169 UnbindRequest = 0x42 # not constructed
170 SearchRequest = 0x63
171 SearchResultEntry = 0x64
172 SearchResultDone = 0x65
173 SearchResultReference = 0x66
174 ModifyRequest = 0x67
175 ModifyResponse = 0x68
176 AddRequest = 0x69
177 AddResponse = 0x6A
178 DelRequest = 0x6B
179 DelResponse = 0x6C
180 ModifyDNRequest = 0x6D
181 ModifyDNResponse = 0x6E
182 CompareRequest = 0x6F
183 CompareResponse = 0x70
184 AbandonRequest = 0x71
185 ExtendedRequest = 0x72
186 ExtendedResponse = 0x73
187
188
189# Bind operation
190# https://datatracker.ietf.org/doc/html/rfc1777#section-4.1
191
192
193class ASN1_Class_LDAP_Authentication(ASN1_Class):
194 name = "LDAP Authentication"
195 # CONTEXT-SPECIFIC = 0x80
196 simple = 0x80
197 krbv42LDAP = 0x81
198 krbv42DSA = 0x82
199 sasl = 0xA3 # CONTEXT-SPECIFIC | CONSTRUCTED
200 # [MS-ADTS] sect 5.1.1.1
201 sicilyPackageDiscovery = 0x89
202 sicilyNegotiate = 0x8A
203 sicilyResponse = 0x8B
204
205
206# simple
207class LDAP_Authentication_simple(ASN1_STRING):
208 tag = ASN1_Class_LDAP_Authentication.simple
209
210
211class BERcodec_LDAP_Authentication_simple(BERcodec_STRING):
212 tag = ASN1_Class_LDAP_Authentication.simple
213
214
215class ASN1F_LDAP_Authentication_simple(ASN1F_STRING):
216 ASN1_tag = ASN1_Class_LDAP_Authentication.simple
217
218
219# krbv42LDAP
220class LDAP_Authentication_krbv42LDAP(ASN1_STRING):
221 tag = ASN1_Class_LDAP_Authentication.krbv42LDAP
222
223
224class BERcodec_LDAP_Authentication_krbv42LDAP(BERcodec_STRING):
225 tag = ASN1_Class_LDAP_Authentication.krbv42LDAP
226
227
228class ASN1F_LDAP_Authentication_krbv42LDAP(ASN1F_STRING):
229 ASN1_tag = ASN1_Class_LDAP_Authentication.krbv42LDAP
230
231
232# krbv42DSA
233class LDAP_Authentication_krbv42DSA(ASN1_STRING):
234 tag = ASN1_Class_LDAP_Authentication.krbv42DSA
235
236
237class BERcodec_LDAP_Authentication_krbv42DSA(BERcodec_STRING):
238 tag = ASN1_Class_LDAP_Authentication.krbv42DSA
239
240
241class ASN1F_LDAP_Authentication_krbv42DSA(ASN1F_STRING):
242 ASN1_tag = ASN1_Class_LDAP_Authentication.krbv42DSA
243
244
245# sicilyPackageDiscovery
246class LDAP_Authentication_sicilyPackageDiscovery(ASN1_STRING):
247 tag = ASN1_Class_LDAP_Authentication.sicilyPackageDiscovery
248
249
250class BERcodec_LDAP_Authentication_sicilyPackageDiscovery(BERcodec_STRING):
251 tag = ASN1_Class_LDAP_Authentication.sicilyPackageDiscovery
252
253
254class ASN1F_LDAP_Authentication_sicilyPackageDiscovery(ASN1F_STRING):
255 ASN1_tag = ASN1_Class_LDAP_Authentication.sicilyPackageDiscovery
256
257
258# sicilyNegotiate
259class LDAP_Authentication_sicilyNegotiate(ASN1_STRING):
260 tag = ASN1_Class_LDAP_Authentication.sicilyNegotiate
261
262
263class BERcodec_LDAP_Authentication_sicilyNegotiate(BERcodec_STRING):
264 tag = ASN1_Class_LDAP_Authentication.sicilyNegotiate
265
266
267class ASN1F_LDAP_Authentication_sicilyNegotiate(ASN1F_STRING):
268 ASN1_tag = ASN1_Class_LDAP_Authentication.sicilyNegotiate
269
270
271# sicilyResponse
272class LDAP_Authentication_sicilyResponse(ASN1_STRING):
273 tag = ASN1_Class_LDAP_Authentication.sicilyResponse
274
275
276class BERcodec_LDAP_Authentication_sicilyResponse(BERcodec_STRING):
277 tag = ASN1_Class_LDAP_Authentication.sicilyResponse
278
279
280class ASN1F_LDAP_Authentication_sicilyResponse(ASN1F_STRING):
281 ASN1_tag = ASN1_Class_LDAP_Authentication.sicilyResponse
282
283
284_SASL_MECHANISMS = {b"GSS-SPNEGO": GSSAPI_BLOB, b"GSSAPI": GSSAPI_BLOB}
285
286
287class _SaslCredentialsField(_ASN1FString_PacketField):
288 def m2i(self, pkt, s):
289 val = super(_SaslCredentialsField, self).m2i(pkt, s)
290 if not val[0].val:
291 return val
292 if pkt.mechanism.val in _SASL_MECHANISMS:
293 return (
294 _SASL_MECHANISMS[pkt.mechanism.val](val[0].val, _underlayer=pkt),
295 val[1],
296 )
297 return val
298
299
300class LDAP_Authentication_SaslCredentials(ASN1_Packet):
301 ASN1_codec = ASN1_Codecs.BER
302 ASN1_root = ASN1F_SEQUENCE(
303 LDAPString("mechanism", ""),
304 ASN1F_optional(
305 _SaslCredentialsField("credentials", ""),
306 ),
307 implicit_tag=ASN1_Class_LDAP_Authentication.sasl,
308 )
309
310
311class LDAP_BindRequest(ASN1_Packet):
312 ASN1_codec = ASN1_Codecs.BER
313 ASN1_root = ASN1F_SEQUENCE(
314 ASN1F_INTEGER("version", 3),
315 LDAPDN("bind_name", ""),
316 ASN1F_CHOICE(
317 "authentication",
318 None,
319 ASN1F_LDAP_Authentication_simple,
320 ASN1F_LDAP_Authentication_krbv42LDAP,
321 ASN1F_LDAP_Authentication_krbv42DSA,
322 LDAP_Authentication_SaslCredentials,
323 ),
324 implicit_tag=ASN1_Class_LDAP.BindRequest,
325 )
326
327
328class LDAP_BindResponse(ASN1_Packet):
329 ASN1_codec = ASN1_Codecs.BER
330 ASN1_root = ASN1F_SEQUENCE(
331 *(
332 LDAPResult
333 + (
334 ASN1F_optional(
335 # For GSSAPI, the response is wrapped in
336 # LDAP_Authentication_SaslCredentials
337 ASN1F_STRING("serverSaslCredsWrap", "", implicit_tag=0xA7),
338 ),
339 )
340 + (
341 ASN1F_optional(
342 ASN1F_STRING("serverSaslCreds", "", implicit_tag=0x87),
343 ),
344 )
345 ),
346 implicit_tag=ASN1_Class_LDAP.BindResponse,
347 )
348
349 @property
350 def serverCreds(self):
351 """
352 serverCreds field in SicilyBindResponse
353 """
354 return self.matchedDN.val
355
356 @serverCreds.setter
357 def serverCreds(self, val):
358 """
359 serverCreds field in SicilyBindResponse
360 """
361 self.matchedDN = ASN1_STRING(val)
362
363 @property
364 def serverSaslCredsData(self):
365 """
366 Get serverSaslCreds or serverSaslCredsWrap depending on what's available
367 """
368 if self.serverSaslCredsWrap and self.serverSaslCredsWrap.val:
369 wrap = LDAP_Authentication_SaslCredentials(self.serverSaslCredsWrap.val)
370 val = wrap.credentials
371 if isinstance(val, ASN1_STRING):
372 return val.val
373 return bytes(val)
374 elif self.serverSaslCreds and self.serverSaslCreds.val:
375 return self.serverSaslCreds.val
376 else:
377 return None
378
379
380# Unbind operation
381# https://datatracker.ietf.org/doc/html/rfc1777#section-4.2
382
383
384class LDAP_UnbindRequest(ASN1_Packet):
385 ASN1_codec = ASN1_Codecs.BER
386 ASN1_root = ASN1F_SEQUENCE(
387 ASN1F_NULL("info", 0),
388 implicit_tag=ASN1_Class_LDAP.UnbindRequest,
389 )
390
391
392# Search operation
393# https://datatracker.ietf.org/doc/html/rfc1777#section-4.3
394
395
396class LDAP_SubstringFilterInitial(ASN1_Packet):
397 ASN1_codec = ASN1_Codecs.BER
398 ASN1_root = LDAPString("initial", "")
399
400
401class LDAP_SubstringFilterAny(ASN1_Packet):
402 ASN1_codec = ASN1_Codecs.BER
403 ASN1_root = LDAPString("any", "")
404
405
406class LDAP_SubstringFilterFinal(ASN1_Packet):
407 ASN1_codec = ASN1_Codecs.BER
408 ASN1_root = LDAPString("final", "")
409
410
411class LDAP_SubstringFilterStr(ASN1_Packet):
412 ASN1_codec = ASN1_Codecs.BER
413 ASN1_root = ASN1F_CHOICE(
414 "str",
415 ASN1_STRING(""),
416 ASN1F_PACKET(
417 "initial",
418 LDAP_SubstringFilterInitial(),
419 LDAP_SubstringFilterInitial,
420 implicit_tag=0x80,
421 ),
422 ASN1F_PACKET(
423 "any", LDAP_SubstringFilterAny(), LDAP_SubstringFilterAny, implicit_tag=0x81
424 ),
425 ASN1F_PACKET(
426 "final",
427 LDAP_SubstringFilterFinal(),
428 LDAP_SubstringFilterFinal,
429 implicit_tag=0x82,
430 ),
431 )
432
433
434class LDAP_SubstringFilter(ASN1_Packet):
435 ASN1_codec = ASN1_Codecs.BER
436 ASN1_root = ASN1F_SEQUENCE(
437 AttributeType("type", ""),
438 ASN1F_SEQUENCE_OF("filters", [], LDAP_SubstringFilterStr),
439 )
440
441
442_LDAP_Filter = lambda *args, **kwargs: LDAP_Filter(*args, **kwargs)
443
444
445class LDAP_FilterAnd(ASN1_Packet):
446 ASN1_codec = ASN1_Codecs.BER
447 ASN1_root = ASN1F_SET_OF("and_", [], _LDAP_Filter)
448
449
450class LDAP_FilterOr(ASN1_Packet):
451 ASN1_codec = ASN1_Codecs.BER
452 ASN1_root = ASN1F_SET_OF("or_", [], _LDAP_Filter)
453
454
455class LDAP_FilterPresent(ASN1_Packet):
456 ASN1_codec = ASN1_Codecs.BER
457 ASN1_root = AttributeType("present", "")
458
459
460class LDAP_FilterEqual(ASN1_Packet):
461 ASN1_codec = ASN1_Codecs.BER
462 ASN1_root = AttributeValueAssertion.ASN1_root
463
464
465class LDAP_FilterGreaterOrEqual(ASN1_Packet):
466 ASN1_codec = ASN1_Codecs.BER
467 ASN1_root = AttributeValueAssertion.ASN1_root
468
469
470class LDAP_FilterLesserOrEqual(ASN1_Packet):
471 ASN1_codec = ASN1_Codecs.BER
472 ASN1_root = AttributeValueAssertion.ASN1_root
473
474
475class LDAP_FilterLessOrEqual(ASN1_Packet):
476 ASN1_codec = ASN1_Codecs.BER
477 ASN1_root = AttributeValueAssertion.ASN1_root
478
479
480class LDAP_FilterApproxMatch(ASN1_Packet):
481 ASN1_codec = ASN1_Codecs.BER
482 ASN1_root = AttributeValueAssertion.ASN1_root
483
484
485class ASN1_Class_LDAP_Filter(ASN1_Class):
486 name = "LDAP Filter"
487 # CONTEXT-SPECIFIC + CONSTRUCTED = 0x80 | 0x20
488 And = 0xA0
489 Or = 0xA1
490 Not = 0xA2
491 EqualityMatch = 0xA3
492 Substrings = 0xA4
493 GreaterOrEqual = 0xA5
494 LessOrEqual = 0xA6
495 Present = 0x87 # not constructed
496 ApproxMatch = 0xA8
497
498
499class LDAP_Filter(ASN1_Packet):
500 ASN1_codec = ASN1_Codecs.BER
501 ASN1_root = ASN1F_CHOICE(
502 "filter",
503 LDAP_FilterPresent(),
504 ASN1F_PACKET(
505 "and_", None, LDAP_FilterAnd, implicit_tag=ASN1_Class_LDAP_Filter.And
506 ),
507 ASN1F_PACKET(
508 "or_", None, LDAP_FilterOr, implicit_tag=ASN1_Class_LDAP_Filter.Or
509 ),
510 ASN1F_PACKET(
511 "not_", None, _LDAP_Filter, implicit_tag=ASN1_Class_LDAP_Filter.Not
512 ),
513 ASN1F_PACKET(
514 "equalityMatch",
515 None,
516 LDAP_FilterEqual,
517 implicit_tag=ASN1_Class_LDAP_Filter.EqualityMatch,
518 ),
519 ASN1F_PACKET(
520 "substrings",
521 None,
522 LDAP_SubstringFilter,
523 implicit_tag=ASN1_Class_LDAP_Filter.Substrings,
524 ),
525 ASN1F_PACKET(
526 "greaterOrEqual",
527 None,
528 LDAP_FilterGreaterOrEqual,
529 implicit_tag=ASN1_Class_LDAP_Filter.GreaterOrEqual,
530 ),
531 ASN1F_PACKET(
532 "lessOrEqual",
533 None,
534 LDAP_FilterLessOrEqual,
535 implicit_tag=ASN1_Class_LDAP_Filter.LessOrEqual,
536 ),
537 ASN1F_PACKET(
538 "present",
539 None,
540 LDAP_FilterPresent,
541 implicit_tag=ASN1_Class_LDAP_Filter.Present,
542 ),
543 ASN1F_PACKET(
544 "approxMatch",
545 None,
546 LDAP_FilterApproxMatch,
547 implicit_tag=ASN1_Class_LDAP_Filter.ApproxMatch,
548 ),
549 )
550
551
552class LDAP_SearchRequestAttribute(ASN1_Packet):
553 ASN1_codec = ASN1_Codecs.BER
554 ASN1_root = AttributeType("type", "")
555
556
557class LDAP_SearchRequest(ASN1_Packet):
558 ASN1_codec = ASN1_Codecs.BER
559 ASN1_root = ASN1F_SEQUENCE(
560 LDAPDN("baseObject", ""),
561 ASN1F_ENUMERATED(
562 "scope", 0, {0: "baseObject", 1: "singleLevel", 2: "wholeSubtree"}
563 ),
564 ASN1F_ENUMERATED(
565 "derefAliases",
566 0,
567 {
568 0: "neverDerefAliases",
569 1: "derefInSearching",
570 2: "derefFindingBaseObj",
571 3: "derefAlways",
572 },
573 ),
574 ASN1F_INTEGER("sizeLimit", 0),
575 ASN1F_INTEGER("timeLimit", 0),
576 ASN1F_BOOLEAN("attrsOnly", False),
577 ASN1F_PACKET("filter", LDAP_Filter(), LDAP_Filter),
578 ASN1F_SEQUENCE_OF("attributes", [], LDAP_SearchRequestAttribute),
579 implicit_tag=ASN1_Class_LDAP.SearchRequest,
580 )
581
582
583class LDAP_SearchResponseEntryAttributeValue(ASN1_Packet):
584 ASN1_codec = ASN1_Codecs.BER
585 ASN1_root = AttributeValue("value", "")
586
587
588class LDAP_SearchResponseEntryAttribute(ASN1_Packet):
589 ASN1_codec = ASN1_Codecs.BER
590 ASN1_root = ASN1F_SEQUENCE(
591 AttributeType("type", ""),
592 ASN1F_SET_OF("values", [], LDAP_SearchResponseEntryAttributeValue),
593 )
594
595
596class LDAP_SearchResponseEntry(ASN1_Packet):
597 ASN1_codec = ASN1_Codecs.BER
598 ASN1_root = ASN1F_SEQUENCE(
599 LDAPDN("objectName", ""),
600 ASN1F_SEQUENCE_OF(
601 "attributes",
602 LDAP_SearchResponseEntryAttribute(),
603 LDAP_SearchResponseEntryAttribute,
604 ),
605 implicit_tag=ASN1_Class_LDAP.SearchResultEntry,
606 )
607
608
609class LDAP_SearchResponseResultDone(ASN1_Packet):
610 ASN1_codec = ASN1_Codecs.BER
611 ASN1_root = ASN1F_SEQUENCE(
612 *LDAPResult,
613 implicit_tag=ASN1_Class_LDAP.SearchResultDone,
614 )
615
616
617class LDAP_AbandonRequest(ASN1_Packet):
618 ASN1_codec = ASN1_Codecs.BER
619 ASN1_root = ASN1F_SEQUENCE(
620 ASN1F_INTEGER("messageID", 0),
621 implicit_tag=ASN1_Class_LDAP.AbandonRequest,
622 )
623
624
625# LDAP v3
626
627
628class LDAP_Control(ASN1_Packet):
629 ASN1_codec = ASN1_Codecs.BER
630 ASN1_root = ASN1F_SEQUENCE(
631 LDAPOID("controlType", ""),
632 ASN1F_optional(
633 ASN1F_BOOLEAN("criticality", False),
634 ),
635 ASN1F_optional(ASN1F_STRING("controlValue", "")),
636 )
637
638
639# LDAP
640
641
642class LDAP(ASN1_Packet):
643 ASN1_codec = ASN1_Codecs.BER
644 ASN1_root = ASN1F_SEQUENCE(
645 ASN1F_INTEGER("messageID", 0),
646 ASN1F_CHOICE(
647 "protocolOp",
648 LDAP_SearchRequest(),
649 LDAP_BindRequest,
650 LDAP_BindResponse,
651 LDAP_SearchRequest,
652 LDAP_SearchResponseEntry,
653 LDAP_SearchResponseResultDone,
654 LDAP_AbandonRequest,
655 LDAP_UnbindRequest,
656 ),
657 # LDAP v3 only
658 ASN1F_optional(
659 ASN1F_SEQUENCE_OF("Controls", None, LDAP_Control, implicit_tag=0xA0)
660 ),
661 )
662
663 def answers(self, other):
664 return isinstance(other, LDAP) and other.messageID == self.messageID
665
666 def mysummary(self):
667 return (
668 "%s(%s)"
669 % (
670 self.protocolOp.__class__.__name__.replace("_", " "),
671 self.messageID.val,
672 ),
673 [LDAP],
674 )
675
676
677bind_layers(LDAP, LDAP)
678
679bind_bottom_up(TCP, LDAP, dport=389)
680bind_bottom_up(TCP, LDAP, sport=389)
681bind_bottom_up(TCP, LDAP, dport=3268)
682bind_bottom_up(TCP, LDAP, sport=3268)
683bind_layers(TCP, LDAP, sport=389, dport=389)
684
685# CLDAP - rfc1798
686
687
688class CLDAP(ASN1_Packet):
689 ASN1_codec = ASN1_Codecs.BER
690 ASN1_root = ASN1F_SEQUENCE(
691 LDAP.ASN1_root.seq[0], # messageID
692 ASN1F_optional(
693 LDAPDN("user", ""),
694 ),
695 LDAP.ASN1_root.seq[1], # protocolOp
696 )
697
698 def answers(self, other):
699 return isinstance(other, CLDAP) and other.messageID == self.messageID
700
701
702bind_layers(CLDAP, CLDAP)
703
704bind_bottom_up(UDP, CLDAP, dport=389)
705bind_bottom_up(UDP, CLDAP, sport=389)
706bind_layers(UDP, CLDAP, sport=389, dport=389)
707
708
709# Small CLDAP Answering machine: [MS-ADTS] 6.3.3 - Ldap ping
710
711
712class LdapPing_am(AnsweringMachine):
713 function_name = "ldappingd"
714 filter = "udp port 389 or 138"
715 send_function = staticmethod(send)
716
717 def parse_options(
718 self,
719 NetbiosDomainName="DOMAIN",
720 DomainGuid=uuid.UUID("192bc4b3-0085-4521-83fe-062913ef59f2"),
721 DcSiteName="Default-First-Site-Name",
722 NetbiosComputerName="SRV1",
723 DnsForestName=None,
724 DnsHostName=None,
725 src_ip=None,
726 src_ip6=None,
727 ):
728 self.NetbiosDomainName = NetbiosDomainName
729 self.DnsForestName = DnsForestName or (NetbiosDomainName + ".LOCAL")
730 self.DomainGuid = DomainGuid
731 self.DcSiteName = DcSiteName
732 self.NetbiosComputerName = NetbiosComputerName
733 self.DnsHostName = DnsHostName or (
734 NetbiosComputerName + "." + self.DnsForestName
735 )
736 self.src_ip = src_ip
737 self.src_ip6 = src_ip6
738
739 def is_request(self, req):
740 # [MS-ADTS] 6.3.3 - Example:
741 # (&(DnsDomain=abcde.corp.microsoft.com)(Host=abcdefgh-dev)(User=abcdefgh-
742 # dev$)(AAC=\80\00\00\00)(DomainGuid=\3b\b0\21\ca\d3\6d\d1\11\8a\7d\b8\df\b1\56\87\1f)(NtVer
743 # =\06\00\00\00))
744 if NBTDatagram in req:
745 # special case: mailslot ping
746 from scapy.layers.smb import SMBMailslot_Write, NETLOGON_SAM_LOGON_REQUEST
747 try:
748 return (
749 SMBMailslot_Write in req and
750 NETLOGON_SAM_LOGON_REQUEST in req.Data
751 )
752 except AttributeError:
753 return False
754 if CLDAP not in req or not isinstance(req.protocolOp, LDAP_SearchRequest):
755 return False
756 req = req.protocolOp
757 return (
758 req.attributes
759 and req.attributes[0].type.val.lower() == b"netlogon"
760 and req.filter
761 and isinstance(req.filter.filter, LDAP_FilterAnd)
762 and any(
763 x.filter.attributeType.val == b"NtVer" for x in req.filter.filter.and_
764 )
765 )
766
767 def make_reply(self, req):
768 if NBTDatagram in req:
769 # Special case
770 return self.make_mailslot_ping_reply(req)
771 if IPv6 in req:
772 resp = IPv6(dst=req[IPv6].src, src=self.src_ip6 or req[IPv6].dst)
773 else:
774 resp = IP(dst=req[IP].src, src=self.src_ip or req[IP].dst)
775 resp /= UDP(sport=req.dport, dport=req.sport)
776 # get the DnsDomainName from the request
777 try:
778 DnsDomainName = next(
779 x.filter.attributeValue.val
780 for x in req.protocolOp.filter.filter.and_
781 if x.filter.attributeType.val == b"DnsDomain"
782 )
783 except StopIteration:
784 return
785 return (
786 resp
787 / CLDAP(
788 protocolOp=LDAP_SearchResponseEntry(
789 attributes=[
790 LDAP_SearchResponseEntryAttribute(
791 values=[
792 LDAP_SearchResponseEntryAttributeValue(
793 value=ASN1_STRING(
794 val=bytes(
795 NETLOGON_SAM_LOGON_RESPONSE_EX(
796 # Mandatory fields
797 DnsDomainName=DnsDomainName,
798 NtVersion="V1+V5",
799 LmNtToken=65535,
800 Lm20Token=65535,
801 # Below can be customized
802 Flags=0x3F3FD,
803 DomainGuid=self.DomainGuid,
804 DnsForestName=self.DnsForestName,
805 DnsHostName=self.DnsHostName,
806 NetbiosDomainName=self.NetbiosDomainName, # noqa: E501
807 NetbiosComputerName=self.NetbiosComputerName, # noqa: E501
808 UserName=b".",
809 DcSiteName=self.DcSiteName,
810 ClientSiteName=self.DcSiteName,
811 )
812 )
813 )
814 )
815 ],
816 type=ASN1_STRING(b"Netlogon"),
817 )
818 ],
819 ),
820 messageID=req.messageID,
821 user=None,
822 )
823 / CLDAP(
824 protocolOp=LDAP_SearchResponseResultDone(
825 referral=None,
826 resultCode=0,
827 ),
828 messageID=req.messageID,
829 user=None,
830 )
831 )
832
833 def make_mailslot_ping_reply(self, req):
834 # type: (Packet) -> Packet
835 from scapy.layers.smb import (
836 SMBMailslot_Write,
837 SMB_Header,
838 DcSockAddr,
839 NETLOGON_SAM_LOGON_RESPONSE_EX,
840 )
841 resp = IP(dst=req[IP].src) / UDP(
842 sport=req.dport,
843 dport=req.sport,
844 )
845 address = self.src_ip or get_if_addr(self.optsniff.get("iface", conf.iface))
846 resp /= NBTDatagram(
847 SourceName=req.DestinationName,
848 SUFFIX1=req.SUFFIX2,
849 DestinationName=req.SourceName,
850 SUFFIX2=req.SUFFIX1,
851 SourceIP=address,
852 ) / SMB_Header() / SMBMailslot_Write(
853 Name=req.Data.MailslotName,
854 )
855 NetbiosDomainName = req.DestinationName.strip()
856 resp.Data = NETLOGON_SAM_LOGON_RESPONSE_EX(
857 # Mandatory fields
858 NetbiosDomainName=NetbiosDomainName,
859 DcSockAddr=DcSockAddr(
860 sin_addr=address,
861 ),
862 NtVersion="V1+V5EX+V5EX_WITH_IP",
863 LmNtToken=65535,
864 Lm20Token=65535,
865 # Below can be customized
866 Flags=0x3F3FD,
867 DomainGuid=self.DomainGuid,
868 DnsForestName=self.DnsForestName,
869 DnsDomainName=self.DnsForestName,
870 DnsHostName=self.DnsHostName,
871 NetbiosComputerName=self.NetbiosComputerName,
872 DcSiteName=self.DcSiteName,
873 ClientSiteName=self.DcSiteName,
874 )
875 return resp
876
877
878_located_dc = collections.namedtuple("LocatedDC", ["ip", "samlogon"])
879_dclocatorcache = conf.netcache.new_cache("dclocator", 600)
880
881
882@conf.commands.register
883def dclocator(
884 realm, qtype="A", mode="ldap", port=None, timeout=1, NtVersion=None, debug=0
885):
886 """
887 Perform a DC Locator as per [MS-ADTS] sect 6.3.6 or RFC4120.
888
889 :param realm: the kerberos realm to locate
890 :param mode: Detect if a server is up and joinable thanks to one of:
891
892 - 'nocheck': Do not check that servers are online.
893 - 'ldap': Use the LDAP ping (CLDAP) per [MS-ADTS]. Default.
894 This will however not work with MIT Kerberos servers.
895 - 'connect': connect to specified port to test the connection.
896
897 :param mode: in connect mode, the port to connect to. (e.g. 88)
898 :param debug: print debug logs
899
900 This is cached in conf.netcache.dclocator.
901 """
902 if NtVersion is None:
903 # Windows' default
904 NtVersion = (
905 0x00000002 # V5
906 | 0x00000004 # V5EX
907 | 0x00000010 # V5EX_WITH_CLOSEST_SITE
908 | 0x01000000 # AVOID_NT4EMUL
909 | 0x20000000 # IP
910 )
911 # Check cache
912 cache_ident = ";".join([realm, qtype, mode, str(NtVersion)]).lower()
913 if cache_ident in _dclocatorcache:
914 return _dclocatorcache[cache_ident]
915 # Perform DNS-Based discovery (6.3.6.1)
916 # 1. SRV records
917 qname = "_kerberos._tcp.dc._msdcs.%s" % realm.lower()
918 if debug:
919 log_runtime.info("DC Locator: requesting SRV for '%s' ..." % qname)
920 try:
921 hosts = [
922 x.target
923 for x in dns_resolve(
924 qname=qname,
925 qtype="SRV",
926 timeout=timeout,
927 )
928 ]
929 except TimeoutError:
930 raise TimeoutError("Resolution of %s timed out" % qname)
931 if not hosts:
932 raise ValueError("No DNS record found for %s" % qname)
933 elif debug:
934 log_runtime.info(
935 "DC Locator: got %s. Resolving %s records ..." % (hosts, qtype)
936 )
937 # 2. A records
938 ips = []
939 for host in hosts:
940 arec = dns_resolve(
941 qname=host,
942 qtype=qtype,
943 timeout=timeout,
944 )
945 if arec:
946 ips.extend(x.rdata for x in arec)
947 if not ips:
948 raise ValueError("Could not get any %s records for %s" % (qtype, hosts))
949 elif debug:
950 log_runtime.info("DC Locator: got %s . Mode: %s" % (ips, mode))
951 # Pick first online host. We have three options
952 if mode == "nocheck":
953 # Don't check anything. Not recommended
954 return _located_dc(ips[0], None)
955 elif mode == "connect":
956 assert port is not None, "Must provide a port in connect mode !"
957 # Compatibility with MIT Kerberos servers
958 for ip in ips: # TODO: "addresses in weighted random order [RFC2782]"
959 if debug:
960 log_runtime.info("DC Locator: connecting to %s on %s ..." % (ip, port))
961 try:
962 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
963 sock.settimeout(timeout)
964 sock.connect((ip, port))
965 # Success
966 result = _located_dc(ip, None)
967 # Cache
968 _dclocatorcache[cache_ident] = result
969 return result
970 except OSError:
971 # Host timed out, No route to host, etc.
972 if debug:
973 log_runtime.info("DC Locator: %s timed out." % ip)
974 continue
975 finally:
976 sock.close()
977 raise ValueError("No host was reachable on port %s among %s" % (port, ips))
978 elif mode == "ldap":
979 # Real 'LDAP Ping' per [MS-ADTS]
980 for ip in ips: # TODO: "addresses in weighted random order [RFC2782]"
981 if debug:
982 log_runtime.info("DC Locator: LDAP Ping %s on ..." % ip)
983 try:
984 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
985 sock.settimeout(timeout)
986 sock.connect((ip, 389))
987 sock = SimpleSocket(sock, CLDAP)
988 pkt = sock.sr1(
989 CLDAP(
990 protocolOp=LDAP_SearchRequest(
991 filter=LDAP_Filter(
992 filter=LDAP_FilterAnd(
993 and_=[
994 LDAP_Filter(
995 filter=LDAP_FilterEqual(
996 attributeType=ASN1_STRING(b"DnsDomain"),
997 attributeValue=ASN1_STRING(realm),
998 )
999 ),
1000 LDAP_Filter(
1001 filter=LDAP_FilterEqual(
1002 attributeType=ASN1_STRING(b"NtVer"),
1003 attributeValue=ASN1_STRING(
1004 struct.pack("<I", NtVersion)
1005 ),
1006 )
1007 ),
1008 ]
1009 )
1010 ),
1011 attributes=[
1012 LDAP_SearchRequestAttribute(
1013 type=ASN1_STRING(b"Netlogon")
1014 )
1015 ],
1016 ),
1017 user=None,
1018 ),
1019 timeout=timeout,
1020 verbose=0,
1021 )
1022 if pkt:
1023 # Check if we have a search response
1024 response = None
1025 if isinstance(pkt.protocolOp, LDAP_SearchResponseEntry):
1026 try:
1027 response = next(
1028 NETLOGON(x.values[0].value.val)
1029 for x in pkt.protocolOp.attributes
1030 if x.type.val == b"Netlogon"
1031 )
1032 except StopIteration:
1033 pass
1034 result = _located_dc(ip, response)
1035 # Cache
1036 _dclocatorcache[cache_ident] = result
1037 return result
1038 except OSError:
1039 # Host timed out, No route to host, etc.
1040 if debug:
1041 log_runtime.info("DC Locator: %s timed out." % ip)
1042 continue
1043 finally:
1044 sock.close()
1045 raise ValueError("No LDAP ping succeeded on any of %s. Try another mode?" % ips)
1046
1047
1048#####################
1049# Basic LDAP client #
1050#####################
1051
1052
1053class LDAP_BIND_MECHS(Enum):
1054 NONE = "NONE"
1055 SIMPLE = "SIMPLE"
1056 SASL_GSSAPI = "GSSAPI"
1057 SASL_GSS_SPNEGO = "GSS-SPNEGO"
1058 SASL_EXTERNAL = "EXTERNAL"
1059 SASL_DIGEST_MD5 = "DIGEST-MD5"
1060 # [MS-ADTS] extension
1061 SICILY = "SICILY"
1062
1063
1064class LDAP_SASL_GSSAPI_SsfCap(Packet):
1065 """
1066 RFC2222 sect 7.2.1 and 7.2.2 negotiate token
1067 """
1068
1069 fields_desc = [
1070 FlagsField(
1071 "supported_security_layers",
1072 0,
1073 -8,
1074 {
1075 # https://github.com/cyrusimap/cyrus-sasl/blob/7e2feaeeb2e37d38cb5fa957d0e8a599ced22612/plugins/gssapi.c#L221
1076 0x01: "NONE",
1077 0x02: "INTEGRITY",
1078 0x04: "CONFIDENTIALITY",
1079 },
1080 ),
1081 ThreeBytesField("max_output_token_size", 0),
1082 ]
1083
1084
1085class LDAP_Client(object):
1086 """
1087 A basic LDAP client
1088
1089 :param mech: one of LDAP_BIND_MECHS
1090 :param ssl: whether to use LDAPS or not
1091 :param ssp: the SSP object to use for binding
1092
1093 :param sign: request signing when binding
1094 :param encrypt: request encryption when binding
1095
1096 Example 1 - SICILY - NTLM::
1097
1098 ssp = NTLMSSP(UPN="Administrator", PASSWORD="Password1!")
1099 client = LDAP_Client(
1100 LDAP_BIND_MECHS.SICILY,
1101 ssp=ssp,
1102 )
1103 client.connect("192.168.0.100")
1104 client.bind()
1105
1106 Example 2 - SASL_GSSAPI - Kerberos::
1107
1108 ssp = KerberosSSP(UPN="Administrator@domain.local", PASSWORD="Password1!",
1109 SPN="ldap/dc1.domain.local")
1110 client = LDAP_Client(
1111 LDAP_BIND_MECHS.SASL_GSSAPI,
1112 ssp=ssp,
1113 )
1114 client.connect("192.168.0.100")
1115 client.bind()
1116
1117 Example 3 - SASL_GSS_SPNEGO - NTLM / Kerberos::
1118
1119 ssp = SPNEGOSSP([
1120 NTLMSSP(UPN="Administrator", PASSWORD="Password1!"),
1121 KerberosSSP(UPN="Administrator@domain.local", PASSWORD="Password1!",
1122 SPN="ldap/dc1.domain.local"),
1123 ])
1124 client = LDAP_Client(
1125 LDAP_BIND_MECHS.SASL_GSS_SPNEGO,
1126 ssp=ssp,
1127 )
1128 client.connect("192.168.0.100")
1129 client.bind()
1130
1131 Example 4 - Simple bind::
1132
1133 client = LDAP_Client(LDAP_BIND_MECHS.SIMPLE)
1134 client.connect("192.168.0.100")
1135 client.bind(simple_username="Administrator",
1136 simple_password="Password1!")
1137 """
1138
1139 def __init__(
1140 self,
1141 mech,
1142 verb=True,
1143 ssl=False,
1144 sslcontext=None,
1145 ssp=None,
1146 sign=False,
1147 encrypt=False,
1148 ):
1149 self.sock = None
1150 self.mech = mech
1151 self.verb = verb
1152 self.ssl = ssl
1153 self.sslcontext = sslcontext
1154 self.ssp = ssp # type: SSP
1155 assert isinstance(mech, LDAP_BIND_MECHS)
1156 if mech == LDAP_BIND_MECHS.SASL_GSSAPI:
1157 from scapy.layers.kerberos import KerberosSSP
1158
1159 if not isinstance(self.ssp, KerberosSSP):
1160 raise ValueError("Only raw KerberosSSP is supported with SASL_GSSAPI !")
1161 elif mech == LDAP_BIND_MECHS.SASL_GSS_SPNEGO:
1162 from scapy.layers.spnego import SPNEGOSSP
1163
1164 if not isinstance(self.ssp, SPNEGOSSP):
1165 raise ValueError("Only SPNEGOSSP is supported with SASL_GSS_SPNEGO !")
1166 elif mech == LDAP_BIND_MECHS.SICILY:
1167 from scapy.layers.ntlm import NTLMSSP
1168
1169 if not isinstance(self.ssp, NTLMSSP):
1170 raise ValueError("Only raw NTLMSSP is supported with SICILY !")
1171 if self.ssp is not None and mech in [
1172 LDAP_BIND_MECHS.NONE,
1173 LDAP_BIND_MECHS.SIMPLE,
1174 ]:
1175 raise ValueError("%s cannot be used with a ssp !" % mech.value)
1176 self.sspcontext = None
1177 self.sign = sign
1178 self.encrypt = encrypt
1179 self.messageID = 0
1180
1181 def connect(self, ip, port=None, timeout=5):
1182 """
1183 Initiate a connection
1184 """
1185 if port is None:
1186 if self.ssl:
1187 port = 636
1188 else:
1189 port = 389
1190 sock = socket.socket()
1191 sock.settimeout(timeout)
1192 if self.verb:
1193 print(
1194 "\u2503 Connecting to %s on port %s%s..."
1195 % (
1196 ip,
1197 port,
1198 " with SSL" if self.ssl else "",
1199 )
1200 )
1201 sock.connect((ip, port))
1202 if self.verb:
1203 print(
1204 conf.color_theme.green(
1205 "\u2514 Connected from %s" % repr(sock.getsockname())
1206 )
1207 )
1208 if self.ssl:
1209 if self.sslcontext is None:
1210 context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
1211 context.check_hostname = False
1212 context.verify_mode = ssl.CERT_NONE
1213 else:
1214 context = self.sslcontext
1215 sock = context.wrap_socket(sock)
1216 self.sock = StreamSocket(sock, LDAP)
1217
1218 def sr1(self, protocolOp, controls=None, **kwargs):
1219 self.messageID += 1
1220 if self.verb:
1221 print(conf.color_theme.opening(">> %s" % protocolOp.__class__.__name__))
1222 resp = self.sock.sr1(
1223 LDAP(
1224 messageID=self.messageID,
1225 protocolOp=protocolOp,
1226 Controls=controls,
1227 ),
1228 verbose=0,
1229 **kwargs,
1230 )
1231 if self.verb:
1232 print(
1233 conf.color_theme.success(
1234 "<< %s"
1235 % (
1236 resp.protocolOp.__class__.__name__
1237 if LDAP in resp
1238 else resp.__class__.__name__
1239 )
1240 )
1241 )
1242 return resp
1243
1244 def bind(self, simple_username=None, simple_password=None):
1245 """
1246 Send Bind request.
1247 This acts differently based on the :mech: provided during initialization.
1248 """
1249 if self.mech == LDAP_BIND_MECHS.SIMPLE:
1250 # Simple binding
1251 resp = self.sr1(
1252 LDAP_BindRequest(
1253 bind_name=ASN1_STRING(simple_username or ""),
1254 authentication=LDAP_Authentication_simple(
1255 simple_password or "",
1256 ),
1257 )
1258 )
1259 if (
1260 LDAP not in resp
1261 or not isinstance(resp.protocolOp, LDAP_BindResponse)
1262 or resp.protocolOp.resultCode != 0
1263 ):
1264 if self.verb:
1265 resp.show()
1266 raise RuntimeError("LDAP simple bind failed !")
1267 elif self.mech == LDAP_BIND_MECHS.SICILY:
1268 # [MS-ADTS] sect 5.1.1.1.3
1269 # 1. Package Discovery
1270 resp = self.sr1(
1271 LDAP_BindRequest(
1272 bind_name=ASN1_STRING(b""),
1273 authentication=LDAP_Authentication_sicilyPackageDiscovery(b""),
1274 )
1275 )
1276 if resp.protocolOp.resultCode != 0:
1277 resp.show()
1278 raise RuntimeError("Sicily package discovery failed !")
1279 # 2. First exchange: Negotiate
1280 self.sspcontext, token, status = self.ssp.GSS_Init_sec_context(
1281 self.sspcontext,
1282 req_flags=(
1283 GSS_C_FLAGS.GSS_C_REPLAY_FLAG
1284 | GSS_C_FLAGS.GSS_C_SEQUENCE_FLAG
1285 | GSS_C_FLAGS.GSS_C_MUTUAL_FLAG
1286 | (GSS_C_FLAGS.GSS_C_INTEG_FLAG if self.sign else 0)
1287 | (GSS_C_FLAGS.GSS_C_CONF_FLAG if self.encrypt else 0)
1288 ),
1289 )
1290 resp = self.sr1(
1291 LDAP_BindRequest(
1292 bind_name=ASN1_STRING(b"NTLM"),
1293 authentication=LDAP_Authentication_sicilyNegotiate(
1294 bytes(token),
1295 ),
1296 )
1297 )
1298 val = resp.protocolOp.serverCreds
1299 if not val:
1300 resp.show()
1301 raise RuntimeError("Sicily negotiate failed !")
1302 # 3. Second exchange: Response
1303 self.sspcontext, token, status = self.ssp.GSS_Init_sec_context(
1304 self.sspcontext, GSSAPI_BLOB(val)
1305 )
1306 resp = self.sr1(
1307 LDAP_BindRequest(
1308 bind_name=ASN1_STRING(b"NTLM"),
1309 authentication=LDAP_Authentication_sicilyResponse(
1310 bytes(token),
1311 ),
1312 )
1313 )
1314 if resp.protocolOp.resultCode != 0:
1315 resp.show()
1316 raise RuntimeError("Sicily response failed !")
1317 elif self.mech in [
1318 LDAP_BIND_MECHS.SASL_GSS_SPNEGO,
1319 LDAP_BIND_MECHS.SASL_GSSAPI,
1320 ]:
1321 # GSSAPI or SPNEGO
1322 self.sspcontext, token, status = self.ssp.GSS_Init_sec_context(
1323 self.sspcontext,
1324 req_flags=(
1325 GSS_C_FLAGS.GSS_C_REPLAY_FLAG
1326 | GSS_C_FLAGS.GSS_C_SEQUENCE_FLAG
1327 | GSS_C_FLAGS.GSS_C_MUTUAL_FLAG
1328 | (GSS_C_FLAGS.GSS_C_INTEG_FLAG if self.sign else 0)
1329 | (GSS_C_FLAGS.GSS_C_CONF_FLAG if self.encrypt else 0)
1330 ),
1331 )
1332 while token:
1333 resp = self.sr1(
1334 LDAP_BindRequest(
1335 bind_name=ASN1_STRING(b""),
1336 authentication=LDAP_Authentication_SaslCredentials(
1337 mechanism=ASN1_STRING(self.mech.value),
1338 credentials=ASN1_STRING(bytes(token)),
1339 ),
1340 )
1341 )
1342 if not isinstance(resp.protocolOp, LDAP_BindResponse):
1343 if self.verb:
1344 print("%s bind failed !" % self.mech.name)
1345 resp.show()
1346 return
1347 val = resp.protocolOp.serverSaslCredsData
1348 if not val:
1349 status = resp.protocolOp.resultCode
1350 break
1351 self.sspcontext, token, status = self.ssp.GSS_Init_sec_context(
1352 self.sspcontext, GSSAPI_BLOB(val)
1353 )
1354 if status != GSS_S_COMPLETE:
1355 raise RuntimeError("%s bind returned %s !" % (self.mech.name, status))
1356 elif self.mech == LDAP_BIND_MECHS.SASL_GSSAPI:
1357 # GSSAPI has 2 extra exchanges
1358 # https://datatracker.ietf.org/doc/html/rfc2222#section-7.2.1
1359 resp = self.sr1(
1360 LDAP_BindRequest(
1361 bind_name=ASN1_STRING(b""),
1362 authentication=LDAP_Authentication_SaslCredentials(
1363 mechanism=ASN1_STRING(self.mech.value),
1364 credentials=None,
1365 ),
1366 )
1367 )
1368 # Parse server-supported layers
1369 saslOptions = GSSAPI_BLOB_SIGNATURE(resp.protocolOp.serverSaslCredsData)
1370 saslOptions.show()
1371 saslOptions = LDAP_SASL_GSSAPI_SsfCap(
1372 self.ssp.GSS_Unwrap(self.sspcontext, b"", saslOptions)
1373 )
1374 if self.sign and not saslOptions.supported_security_layers.INTEGRITY:
1375 raise RuntimeError("GSSAPI SASL failed to negotiate INTEGRITY !")
1376 if (
1377 self.encrypt
1378 and not saslOptions.supported_security_layers.CONFIDENTIALITY
1379 ):
1380 raise RuntimeError("GSSAPI SASL failed to negotiate CONFIDENTIALITY !")
1381 # Announce client-supported layers
1382 saslOptions = LDAP_SASL_GSSAPI_SsfCap(
1383 supported_security_layers=(
1384 "NONE"
1385 + ("+INTEGRITY" if self.sign else "")
1386 + ("+CONFIDENTIALITY" if self.encrypt else "")
1387 ),
1388 max_output_token_size=0xA00000,
1389 )
1390 resp = self.sr1(
1391 LDAP_BindRequest(
1392 bind_name=ASN1_STRING(b""),
1393 authentication=LDAP_Authentication_SaslCredentials(
1394 mechanism=ASN1_STRING(self.mech.value),
1395 credentials=self.ssp.GSS_Wrap(
1396 self.sspcontext, bytes(saslOptions), False
1397 )[1],
1398 ),
1399 )
1400 )
1401 if resp.protocolOp.resultCode != 0:
1402 resp.show()
1403 raise RuntimeError(
1404 "GSSAPI SASL failed to negotiate client security flags !"
1405 )
1406 if self.verb:
1407 print("%s bind succeeded !" % self.mech.name)
1408
1409 def close(self):
1410 if self.verb:
1411 print("X Connection closed\n")
1412 self.sock.close()