Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483

484

485

486

487

488

489

490

491

492

493

494

495

496

497

498

499

500

501

502

503

504

505

506

507

508

509

510

511

512

513

514

515

516

517

518

519

520

521

522

523

524

525

526

527

528

529

530

531

532

533

534

535

536

537

538

539

540

541

542

543

544

545

546

547

548

549

550

551

552

553

554

555

556

557

558

559

560

561

562

563

564

565

566

567

568

569

570

571

572

573

574

575

576

577

578

579

580

581

582

583

584

585

586

587

588

589

590

591

592

593

594

595

596

597

598

599

600

601

602

603

604

605

606

607

608

609

610

611

612

613

614

615

616

617

618

619

620

621

622

623

624

625

626

627

628

629

630

631

632

633

634

635

636

637

638

639

640

641

642

643

644

645

646

647

648

649

650

# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 

# 

# This software is provided under under a slightly modified version 

# of the Apache Software License. See the accompanying LICENSE file 

# for more information. 

# 

# Authors: Alberto Solino (@agsolino) 

# Kacper Nowak (@kacpern) 

# 

# Description: 

# RFC 4511 Minimalistic implementation. We don't need much functionality yet 

# If we need more complex use cases we might opt to use a third party implementation 

# Keep in mind the APIs are still unstable, might require to re-write your scripts 

# as we change them. 

# Adding [MS-ADTS] specific functionality 

# 

# ToDo: 

# [x] Implement Paging Search, especially important for big requests 

# 

 

import os 

import re 

import socket 

from binascii import unhexlify 

 

from pyasn1.codec.ber import encoder, decoder 

from pyasn1.error import SubstrateUnderrunError 

from pyasn1.type.univ import noValue 

 

from impacket import LOG 

from impacket.ldap.ldapasn1 import Filter, Control, SimplePagedResultsControl, ResultCode, Scope, DerefAliases, Operation, \ 

KNOWN_CONTROLS, CONTROL_PAGEDRESULTS, NOTIFICATION_DISCONNECT, KNOWN_NOTIFICATIONS, BindRequest, SearchRequest, \ 

SearchResultDone, LDAPMessage 

from impacket.ntlm import getNTLMSSPType1, getNTLMSSPType3 

from impacket.spnego import SPNEGO_NegTokenInit, TypesMech 

 

try: 

import OpenSSL 

from OpenSSL import SSL, crypto 

except: 

LOG.critical("pyOpenSSL is not installed, can't continue") 

raise 

 

__all__ = [ 

'LDAPConnection', 'LDAPFilterSyntaxError', 'LDAPFilterInvalidException', 'LDAPSessionError', 'LDAPSearchError', 

'Control', 'SimplePagedResultsControl', 'ResultCode', 'Scope', 'DerefAliases', 'Operation', 

'CONTROL_PAGEDRESULTS', 'KNOWN_CONTROLS', 'NOTIFICATION_DISCONNECT', 'KNOWN_NOTIFICATIONS', 

] 

 

# https://tools.ietf.org/search/rfc4515#section-3 

DESCRIPTION = r'(?:[a-z][a-z0-9\-]*)' 

NUMERIC_OID = r'(?:(?:\d|[1-9]\d+)(?:\.(?:\d|[1-9]\d+))*)' 

OID = r'(?:%s|%s)' % (DESCRIPTION, NUMERIC_OID) 

OPTIONS = r'(?:(?:;[a-z0-9\-]+)*)' 

ATTRIBUTE = r'(%s%s)' % (OID, OPTIONS) 

DN = r'(:dn)' 

MATCHING_RULE = r'(?::(%s))' % OID 

 

RE_OPERATOR = re.compile(r'([:<>~]?=)') 

RE_ATTRIBUTE = re.compile(r'^%s$' % ATTRIBUTE, re.I) 

RE_EX_ATTRIBUTE_1 = re.compile(r'^%s%s?%s?$' % (ATTRIBUTE, DN, MATCHING_RULE), re.I) 

RE_EX_ATTRIBUTE_2 = re.compile(r'^(){0}%s?%s$' % (DN, MATCHING_RULE), re.I) 

 

 

class LDAPConnection: 

def __init__(self, url, baseDN='', dstIp=None): 

""" 

LDAPConnection class 

 

:param string url: 

:param string baseDN: 

:param string dstIp: 

 

:return: a LDAP instance, if not raises a LDAPSessionError exception 

""" 

self._SSL = False 

self._dstPort = 0 

self._dstHost = 0 

self._socket = None 

self._baseDN = baseDN 

self._messageId = 1 

self._dstIp = dstIp 

 

84 ↛ 88line 84 didn't jump to line 88, because the condition on line 84 was never false if url.startswith('ldap://'): 

self._dstPort = 389 

self._SSL = False 

self._dstHost = url[7:] 

elif url.startswith('ldaps://'): 

self._dstPort = 636 

self._SSL = True 

self._dstHost = url[8:] 

elif url.startswith('gc://'): 

self._dstPort = 3268 

self._SSL = False 

self._dstHost = url[5:] 

else: 

raise LDAPSessionError(errorString="Unknown URL prefix: '%s'" % url) 

 

# Try to connect 

100 ↛ 101line 100 didn't jump to line 101, because the condition on line 100 was never true if self._dstIp is not None: 

targetHost = self._dstIp 

else: 

targetHost = self._dstHost 

 

LOG.debug('Connecting to %s, port %d, SSL %s' % (targetHost, self._dstPort, self._SSL)) 

try: 

af, socktype, proto, _, sa = socket.getaddrinfo(targetHost, self._dstPort, 0, socket.SOCK_STREAM)[0] 

self._socket = socket.socket(af, socktype, proto) 

except socket.error as e: 

raise socket.error('Connection error (%s:%d)' % (targetHost, 88), e) 

 

112 ↛ 116line 112 didn't jump to line 116, because the condition on line 112 was never false if self._SSL is False: 

self._socket.connect(sa) 

else: 

# Switching to TLS now 

ctx = SSL.Context(SSL.TLSv1_METHOD) 

# ctx.set_cipher_list('RC4') 

self._socket = SSL.Connection(ctx, self._socket) 

self._socket.connect(sa) 

self._socket.do_handshake() 

 

def kerberosLogin(self, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, TGT=None, 

TGS=None, useCache=True): 

""" 

logins into the target system explicitly using Kerberos. Hashes are used if RC4_HMAC is supported. 

 

:param string user: username 

:param string password: password for the user 

:param string domain: domain where the account is valid for (required) 

:param string lmhash: LMHASH used to authenticate using hashes (password is not used) 

:param string nthash: NTHASH used to authenticate using hashes (password is not used) 

:param string aesKey: aes256-cts-hmac-sha1-96 or aes128-cts-hmac-sha1-96 used for Kerberos authentication 

:param string kdcHost: hostname or IP Address for the KDC. If None, the domain will be used (it needs to resolve tho) 

:param struct TGT: If there's a TGT available, send the structure here and it will be used 

:param struct TGS: same for TGS. See smb3.py for the format 

:param bool useCache: whether or not we should use the ccache for credentials lookup. If TGT or TGS are specified this is False 

 

:return: True, raises a LDAPSessionError if error. 

""" 

 

if lmhash != '' or nthash != '': 

142 ↛ 143line 142 didn't jump to line 143, because the condition on line 142 was never true if len(lmhash) % 2: 

lmhash = '0' + lmhash 

144 ↛ 145line 144 didn't jump to line 145, because the condition on line 144 was never true if len(nthash) % 2: 

nthash = '0' + nthash 

try: # just in case they were converted already 

lmhash = unhexlify(lmhash) 

nthash = unhexlify(nthash) 

except TypeError: 

pass 

 

# Importing down here so pyasn1 is not required if kerberos is not used. 

from impacket.krb5.ccache import CCache 

from impacket.krb5.asn1 import AP_REQ, Authenticator, TGS_REP, seq_set 

from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS 

from impacket.krb5 import constants 

from impacket.krb5.types import Principal, KerberosTime, Ticket 

import datetime 

 

160 ↛ 161line 160 didn't jump to line 161, because the condition on line 160 was never true if TGT is not None or TGS is not None: 

useCache = False 

 

163 ↛ 200line 163 didn't jump to line 200, because the condition on line 163 was never false if useCache: 

try: 

ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) 

except: 

# No cache present 

pass 

else: 

# retrieve domain information from CCache file if needed 

if domain == '': 

domain = ccache.principal.realm['data'].decode('utf-8') 

LOG.debug('Domain retrieved from CCache: %s' % domain) 

 

LOG.debug('Using Kerberos Cache: %s' % os.getenv('KRB5CCNAME')) 

principal = 'ldap/%s@%s' % (self._dstHost.upper(), domain.upper()) 

creds = ccache.getCredential(principal) 

if creds is None: 

# Let's try for the TGT and go from there 

principal = 'krbtgt/%s@%s' % (domain.upper(), domain.upper()) 

creds = ccache.getCredential(principal) 

if creds is not None: 

TGT = creds.toTGT() 

LOG.debug('Using TGT from cache') 

else: 

LOG.debug('No valid credentials found in cache') 

else: 

TGS = creds.toTGS(principal) 

LOG.debug('Using TGS from cache') 

 

# retrieve user information from CCache file if needed 

if user == '' and creds is not None: 

user = creds['client'].prettyPrint().split(b'@')[0] 

LOG.debug('Username retrieved from CCache: %s' % user) 

elif user == '' and len(ccache.principal.components) > 0: 

user = ccache.principal.components[0]['data'] 

LOG.debug('Username retrieved from CCache: %s' % user) 

 

# First of all, we need to get a TGT for the user 

userName = Principal(user, type=constants.PrincipalNameType.NT_PRINCIPAL.value) 

201 ↛ 206line 201 didn't jump to line 206, because the condition on line 201 was never false if TGT is None: 

202 ↛ 210line 202 didn't jump to line 210, because the condition on line 202 was never false if TGS is None: 

tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, password, domain, lmhash, nthash, 

aesKey, kdcHost) 

else: 

tgt = TGT['KDC_REP'] 

cipher = TGT['cipher'] 

sessionKey = TGT['sessionKey'] 

 

210 ↛ 215line 210 didn't jump to line 215, because the condition on line 210 was never false if TGS is None: 

serverName = Principal('ldap/%s' % self._dstHost, type=constants.PrincipalNameType.NT_SRV_INST.value) 

tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, kdcHost, tgt, cipher, 

sessionKey) 

else: 

tgs = TGS['KDC_REP'] 

cipher = TGS['cipher'] 

sessionKey = TGS['sessionKey'] 

 

# Let's build a NegTokenInit with a Kerberos REQ_AP 

 

blob = SPNEGO_NegTokenInit() 

 

# Kerberos 

blob['MechTypes'] = [TypesMech['MS KRB5 - Microsoft Kerberos 5']] 

 

# Let's extract the ticket from the TGS 

tgs = decoder.decode(tgs, asn1Spec=TGS_REP())[0] 

ticket = Ticket() 

ticket.from_asn1(tgs['ticket']) 

 

# Now let's build the AP_REQ 

apReq = AP_REQ() 

apReq['pvno'] = 5 

apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value) 

 

opts = [] 

apReq['ap-options'] = constants.encodeFlags(opts) 

seq_set(apReq, 'ticket', ticket.to_asn1) 

 

authenticator = Authenticator() 

authenticator['authenticator-vno'] = 5 

authenticator['crealm'] = domain 

seq_set(authenticator, 'cname', userName.components_to_asn1) 

now = datetime.datetime.utcnow() 

 

authenticator['cusec'] = now.microsecond 

authenticator['ctime'] = KerberosTime.to_asn1(now) 

 

encodedAuthenticator = encoder.encode(authenticator) 

 

# Key Usage 11 

# AP-REQ Authenticator (includes application authenticator 

# subkey), encrypted with the application session key 

# (Section 5.5.1) 

encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 11, encodedAuthenticator, None) 

 

apReq['authenticator'] = noValue 

apReq['authenticator']['etype'] = cipher.enctype 

apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator 

 

blob['MechToken'] = encoder.encode(apReq) 

 

# Done with the Kerberos saga, now let's get into LDAP 

 

bindRequest = BindRequest() 

bindRequest['version'] = 3 

bindRequest['name'] = user 

bindRequest['authentication']['sasl']['mechanism'] = 'GSS-SPNEGO' 

bindRequest['authentication']['sasl']['credentials'] = blob.getData() 

 

response = self.sendReceive(bindRequest)[0]['protocolOp'] 

 

273 ↛ 274line 273 didn't jump to line 274, because the condition on line 273 was never true if response['bindResponse']['resultCode'] != ResultCode('success'): 

raise LDAPSessionError( 

errorString='Error in bindRequest -> %s: %s' % (response['bindResponse']['resultCode'].prettyPrint(), 

response['bindResponse']['diagnosticMessage']) 

) 

 

return True 

 

def login(self, user='', password='', domain='', lmhash='', nthash='', authenticationChoice='sicilyNegotiate'): 

""" 

logins into the target system 

 

:param string user: username 

:param string password: password for the user 

:param string domain: domain where the account is valid for 

:param string lmhash: LMHASH used to authenticate using hashes (password is not used) 

:param string nthash: NTHASH used to authenticate using hashes (password is not used) 

:param string authenticationChoice: type of authentication protocol to use (default NTLM) 

 

:return: True, raises a LDAPSessionError if error. 

""" 

bindRequest = BindRequest() 

bindRequest['version'] = 3 

 

297 ↛ 298line 297 didn't jump to line 298, because the condition on line 297 was never true if authenticationChoice == 'simple': 

if '.' in domain: 

bindRequest['name'] = user + '@' + domain 

elif domain: 

bindRequest['name'] = domain + '\\' + user 

else: 

bindRequest['name'] = user 

bindRequest['authentication']['simple'] = password 

response = self.sendReceive(bindRequest)[0]['protocolOp'] 

elif authenticationChoice == 'sicilyPackageDiscovery': 

bindRequest['name'] = user 

bindRequest['authentication']['sicilyPackageDiscovery'] = '' 

response = self.sendReceive(bindRequest)[0]['protocolOp'] 

310 ↛ 338line 310 didn't jump to line 338, because the condition on line 310 was never false elif authenticationChoice == 'sicilyNegotiate': 

# Deal with NTLM Authentication 

if lmhash != '' or nthash != '': 

313 ↛ 314line 313 didn't jump to line 314, because the condition on line 313 was never true if len(lmhash) % 2: 

lmhash = '0' + lmhash 

315 ↛ 316line 315 didn't jump to line 316, because the condition on line 315 was never true if len(nthash) % 2: 

nthash = '0' + nthash 

try: # just in case they were converted already 

lmhash = unhexlify(lmhash) 

nthash = unhexlify(nthash) 

except TypeError: 

pass 

 

bindRequest['name'] = user 

 

# NTLM Negotiate 

negotiate = getNTLMSSPType1('', domain) 

bindRequest['authentication']['sicilyNegotiate'] = negotiate.getData() 

response = self.sendReceive(bindRequest)[0]['protocolOp'] 

 

# NTLM Challenge 

type2 = response['bindResponse']['matchedDN'] 

 

# NTLM Auth 

type3, exportedSessionKey = getNTLMSSPType3(negotiate, bytes(type2), user, password, domain, lmhash, nthash) 

bindRequest['authentication']['sicilyResponse'] = type3.getData() 

response = self.sendReceive(bindRequest)[0]['protocolOp'] 

else: 

raise LDAPSessionError(errorString="Unknown authenticationChoice: '%s'" % authenticationChoice) 

 

340 ↛ 341line 340 didn't jump to line 341, because the condition on line 340 was never true if response['bindResponse']['resultCode'] != ResultCode('success'): 

raise LDAPSessionError( 

errorString='Error in bindRequest -> %s: %s' % (response['bindResponse']['resultCode'].prettyPrint(), 

response['bindResponse']['diagnosticMessage']) 

) 

 

return True 

 

def search(self, searchBase=None, scope=None, derefAliases=None, sizeLimit=0, timeLimit=0, typesOnly=False, 

searchFilter='(objectClass=*)', attributes=None, searchControls=None, perRecordCallback=None): 

350 ↛ 352line 350 didn't jump to line 352, because the condition on line 350 was never false if searchBase is None: 

searchBase = self._baseDN 

352 ↛ 354line 352 didn't jump to line 354, because the condition on line 352 was never false if scope is None: 

scope = Scope('wholeSubtree') 

354 ↛ 356line 354 didn't jump to line 356, because the condition on line 354 was never false if derefAliases is None: 

derefAliases = DerefAliases('neverDerefAliases') 

356 ↛ 357line 356 didn't jump to line 357, because the condition on line 356 was never true if attributes is None: 

attributes = [] 

 

searchRequest = SearchRequest() 

searchRequest['baseObject'] = searchBase 

searchRequest['scope'] = scope 

searchRequest['derefAliases'] = derefAliases 

searchRequest['sizeLimit'] = sizeLimit 

searchRequest['timeLimit'] = timeLimit 

searchRequest['typesOnly'] = typesOnly 

searchRequest['filter'] = self._parseFilter(searchFilter) 

searchRequest['attributes'].setComponents(*attributes) 

 

done = False 

answers = [] 

# We keep asking records until we get a SearchResultDone packet and all controls are handled 

while not done: 

response = self.sendReceive(searchRequest, searchControls) 

for message in response: 

searchResult = message['protocolOp'].getComponent() 

if searchResult.isSameTypeWith(SearchResultDone()): 

377 ↛ 380line 377 didn't jump to line 380, because the condition on line 377 was never false if searchResult['resultCode'] == ResultCode('success'): 

done = self._handleControls(searchControls, message['controls']) 

else: 

raise LDAPSearchError( 

error=int(searchResult['resultCode']), 

errorString='Error in searchRequest -> %s: %s' % (searchResult['resultCode'].prettyPrint(), 

searchResult['diagnosticMessage']), 

answers=answers 

) 

else: 

387 ↛ 390line 387 didn't jump to line 390, because the condition on line 387 was never false if perRecordCallback is None: 

answers.append(searchResult) 

else: 

perRecordCallback(searchResult) 

 

return answers 

 

def _handleControls(self, requestControls, responseControls): 

done = True 

396 ↛ 397line 396 didn't jump to line 397, because the condition on line 396 was never true if requestControls is not None: 

for requestControl in requestControls: 

if responseControls is not None: 

for responseControl in responseControls: 

if requestControl['controlType'] == CONTROL_PAGEDRESULTS: 

if responseControl['controlType'] == CONTROL_PAGEDRESULTS: 

if hasattr(responseControl, 'getCookie') is not True: 

responseControl = decoder.decode(encoder.encode(responseControl), 

asn1Spec=KNOWN_CONTROLS[CONTROL_PAGEDRESULTS]())[0] 

if responseControl.getCookie(): 

done = False 

requestControl.setCookie(responseControl.getCookie()) 

break 

else: 

# handle different controls here 

pass 

return done 

 

def close(self): 

if self._socket is not None: 

self._socket.close() 

 

def send(self, request, controls=None): 

message = LDAPMessage() 

message['messageID'] = self._messageId 

message['protocolOp'].setComponentByType(request.getTagSet(), request) 

422 ↛ 423line 422 didn't jump to line 423, because the condition on line 422 was never true if controls is not None: 

message['controls'].setComponents(*controls) 

 

data = encoder.encode(message) 

 

return self._socket.sendall(data) 

 

def recv(self): 

REQUEST_SIZE = 8192 

data = b'' 

done = False 

while not done: 

recvData = self._socket.recv(REQUEST_SIZE) 

435 ↛ 437line 435 didn't jump to line 437, because the condition on line 435 was never false if len(recvData) < REQUEST_SIZE: 

done = True 

data += recvData 

 

response = [] 

while len(data) > 0: 

try: 

message, remaining = decoder.decode(data, asn1Spec=LDAPMessage()) 

except SubstrateUnderrunError: 

# We need more data 

remaining = data + self._socket.recv(REQUEST_SIZE) 

else: 

447 ↛ 448line 447 didn't jump to line 448, because the condition on line 447 was never true if message['messageID'] == 0: # unsolicited notification 

name = message['protocolOp']['extendedResp']['responseName'] or message['responseName'] 

notification = KNOWN_NOTIFICATIONS.get(name, "Unsolicited Notification '%s'" % name) 

if name == NOTIFICATION_DISCONNECT: # Server has disconnected 

self.close() 

raise LDAPSessionError( 

error=int(message['protocolOp']['extendedResp']['resultCode']), 

errorString='%s -> %s: %s' % (notification, 

message['protocolOp']['extendedResp']['resultCode'].prettyPrint(), 

message['protocolOp']['extendedResp']['diagnosticMessage']) 

) 

response.append(message) 

data = remaining 

 

self._messageId += 1 

return response 

 

def sendReceive(self, request, controls=None): 

self.send(request, controls) 

return self.recv() 

 

def _parseFilter(self, filterStr): 

try: 

filterStr = filterStr.decode() 

except AttributeError: 

pass 

filterList = list(reversed(filterStr)) 

searchFilter = self._consumeCompositeFilter(filterList) 

475 ↛ 476line 475 didn't jump to line 476, because the condition on line 475 was never true if filterList: # we have not consumed the whole filter string 

raise LDAPFilterSyntaxError("unexpected token: '%s'" % filterList[-1]) 

return searchFilter 

 

def _consumeCompositeFilter(self, filterList): 

try: 

c = filterList.pop() 

except IndexError: 

raise LDAPFilterSyntaxError('EOL while parsing search filter') 

484 ↛ 485line 484 didn't jump to line 485, because the condition on line 484 was never true if c != '(': # filter must start with a '(' 

filterList.append(c) 

raise LDAPFilterSyntaxError("unexpected token: '%s'" % c) 

 

try: 

operator = filterList.pop() 

except IndexError: 

raise LDAPFilterSyntaxError('EOL while parsing search filter') 

492 ↛ 496line 492 didn't jump to line 496, because the condition on line 492 was never false if operator not in ['!', '&', '|']: # must be simple filter in this case 

filterList.extend([operator, c]) 

return self._consumeSimpleFilter(filterList) 

 

filters = [] 

while True: 

try: 

filters.append(self._consumeCompositeFilter(filterList)) 

except LDAPFilterSyntaxError: 

break 

 

try: 

c = filterList.pop() 

except IndexError: 

raise LDAPFilterSyntaxError('EOL while parsing search filter') 

if c != ')': # filter must end with a ')' 

filterList.append(c) 

raise LDAPFilterSyntaxError("unexpected token: '%s'" % c) 

 

return self._compileCompositeFilter(operator, filters) 

 

def _consumeSimpleFilter(self, filterList): 

try: 

c = filterList.pop() 

except IndexError: 

raise LDAPFilterSyntaxError('EOL while parsing search filter') 

518 ↛ 519line 518 didn't jump to line 519, because the condition on line 518 was never true if c != '(': # filter must start with a '(' 

filterList.append(c) 

raise LDAPFilterSyntaxError("unexpected token: '%s'" % c) 

 

filter = [] 

while True: 

try: 

c = filterList.pop() 

except IndexError: 

raise LDAPFilterSyntaxError('EOL while parsing search filter') 

if c == ')': # we pop till we find a ')' 

break 

530 ↛ 531line 530 didn't jump to line 531, because the condition on line 530 was never true elif c == '(': # should be no unencoded parenthesis 

filterList.append(c) 

raise LDAPFilterSyntaxError("unexpected token: '('") 

else: 

filter.append(c) 

 

filterStr = ''.join(filter) 

try: 

# https://tools.ietf.org/search/rfc4515#section-3 

attribute, operator, value = RE_OPERATOR.split(filterStr, 1) 

except ValueError: 

raise LDAPFilterInvalidException("invalid filter: '(%s)'" % filterStr) 

 

return self._compileSimpleFilter(attribute, operator, value) 

 

@staticmethod 

def _compileCompositeFilter(operator, filters): 

searchFilter = Filter() 

if operator == '!': 

if len(filters) != 1: 

raise LDAPFilterInvalidException("'not' filter must have exactly one element") 

searchFilter['not'].setComponents(*filters) 

elif operator == '&': 

if len(filters) == 0: 

raise LDAPFilterInvalidException("'and' filter must have at least one element") 

searchFilter['and'].setComponents(*filters) 

elif operator == '|': 

if len(filters) == 0: 

raise LDAPFilterInvalidException("'or' filter must have at least one element") 

searchFilter['or'].setComponents(*filters) 

 

return searchFilter 

 

@staticmethod 

def _compileSimpleFilter(attribute, operator, value): 

searchFilter = Filter() 

566 ↛ 567line 566 didn't jump to line 567, because the condition on line 566 was never true if operator == ':=': # extensibleMatch 

match = RE_EX_ATTRIBUTE_1.match(attribute) or RE_EX_ATTRIBUTE_2.match(attribute) 

if not match: 

raise LDAPFilterInvalidException("invalid filter attribute: '%s'" % attribute) 

attribute, dn, matchingRule = match.groups() 

if attribute: 

searchFilter['extensibleMatch']['type'] = attribute 

if dn: 

searchFilter['extensibleMatch']['dnAttributes'] = bool(dn) 

if matchingRule: 

searchFilter['extensibleMatch']['matchingRule'] = matchingRule 

searchFilter['extensibleMatch']['matchValue'] = value 

else: 

579 ↛ 580line 579 didn't jump to line 580, because the condition on line 579 was never true if not RE_ATTRIBUTE.match(attribute): 

raise LDAPFilterInvalidException("invalid filter attribute: '%s'" % attribute) 

if value == '*' and operator == '=': # present 

searchFilter['present'] = attribute 

583 ↛ 584line 583 didn't jump to line 584, because the condition on line 583 was never true elif '*' in value and operator == '=': # substring 

assertions = value.split('*') 

choice = searchFilter['substrings']['substrings'].getComponentType() 

substrings = [] 

if assertions[0]: 

substrings.append(choice.clone().setComponentByName('initial', assertions[0])) 

for assertion in assertions[1:-1]: 

substrings.append(choice.clone().setComponentByName('any', assertion)) 

if assertions[-1]: 

substrings.append(choice.clone().setComponentByName('final', assertions[-1])) 

searchFilter['substrings']['type'] = attribute 

searchFilter['substrings']['substrings'].setComponents(*substrings) 

595 ↛ 605line 595 didn't jump to line 605, because the condition on line 595 was never false elif '*' not in value: # simple 

596 ↛ 598line 596 didn't jump to line 598, because the condition on line 596 was never false if operator == '=': 

searchFilter['equalityMatch'].setComponents(attribute, value) 

elif operator == '~=': 

searchFilter['approxMatch'].setComponents(attribute, value) 

elif operator == '>=': 

searchFilter['greaterOrEqual'].setComponents(attribute, value) 

elif operator == '<=': 

searchFilter['lessOrEqual'].setComponents(attribute, value) 

else: 

raise LDAPFilterInvalidException("invalid filter '(%s%s%s)'" % (attribute, operator, value)) 

 

return searchFilter 

 

 

class LDAPFilterSyntaxError(SyntaxError): 

pass 

 

 

class LDAPFilterInvalidException(Exception): 

pass 

 

 

class LDAPSessionError(Exception): 

""" 

This is the exception every client should catch 

""" 

 

def __init__(self, error=0, packet=0, errorString=''): 

Exception.__init__(self) 

self.error = error 

self.packet = packet 

self.errorString = errorString 

 

def getErrorCode(self): 

return self.error 

 

def getErrorPacket(self): 

return self.packet 

 

def getErrorString(self): 

return self.errorString 

 

def __str__(self): 

return self.errorString 

 

 

class LDAPSearchError(LDAPSessionError): 

def __init__(self, error=0, packet=0, errorString='', answers=None): 

LDAPSessionError.__init__(self, error, packet, errorString) 

if answers is None: 

answers = [] 

self.answers = answers 

 

def getAnswers(self): 

return self.answers