Skip to content

Commit a023ef3

Browse files
authored
Add CRUD methods to ldap (#2097)
* Add CRUD methods to ldap * Remove unpacking list * Fix modify function
1 parent 851fea8 commit a023ef3

File tree

1 file changed

+126
-1
lines changed

1 file changed

+126
-1
lines changed

impacket/ldap/ldap.py

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
from impacket import LOG
3838
from impacket.ldap.ldapasn1 import Filter, Control, SimplePagedResultsControl, ResultCode, Scope, DerefAliases, Operation, \
3939
KNOWN_CONTROLS, CONTROL_PAGEDRESULTS, NOTIFICATION_DISCONNECT, KNOWN_NOTIFICATIONS, BindRequest, SearchRequest, \
40-
SearchResultDone, LDAPMessage
40+
SearchResultDone, LDAPMessage, AddRequest, ModifyRequest, ModifyDNRequest, DelRequest
4141
from impacket.ntlm import getNTLMSSPType1, getNTLMSSPType3, VERSION, hmac_md5, NTLMAuthChallenge
4242
from impacket.spnego import SPNEGO_NegTokenInit, SPNEGO_NegTokenResp, SPNEGOCipher, TypesMech
4343

@@ -68,6 +68,11 @@
6868
RE_EX_ATTRIBUTE_1 = re.compile(r'^%s%s?%s?$' % (ATTRIBUTE, DN, MATCHING_RULE), re.I)
6969
RE_EX_ATTRIBUTE_2 = re.compile(r'^(){0}%s?%s$' % (DN, MATCHING_RULE), re.I)
7070

71+
# LDAP modify operations
72+
MODIFY_ADD = 0
73+
MODIFY_DELETE = 1
74+
MODIFY_REPLACE = 2
75+
MODIFY_INCREMENT = 3
7176

7277
class LDAPConnection:
7378
def __init__(self, url, baseDN='', dstIp=None, signing=True):
@@ -546,6 +551,126 @@ def _handleControls(self, requestControls, responseControls):
546551
pass
547552
return done
548553

554+
def add(self, dn, objectClass, attributes=None, controls=None):
555+
"""
556+
Add an entry to the LDAP directory.
557+
558+
:param dn: Distinguished Name of the entry to add
559+
:param objectClass: Tuple or list of object classes for the entry
560+
:param attributes: Dictionary of attributes for the entry
561+
:param controls: LDAP controls to include in the request
562+
:return: True if the entry was added successfully, else raises LDAPSessionError
563+
"""
564+
addRequest = AddRequest()
565+
addRequest['entry'] = dn
566+
addRequest['attributes'][0]['type'] = 'objectClass'
567+
addRequest['attributes'][0]['vals'].setComponents(*objectClass)
568+
569+
index = 1
570+
for key, value in attributes.items():
571+
addRequest['attributes'][index]['type'] = key
572+
if isinstance(value, list):
573+
addRequest['attributes'][index]['vals'].setComponents(str(val) if isinstance(val, int) else val for val in value)
574+
else:
575+
addRequest['attributes'][index]['vals'].setComponents(str(value) if isinstance(value, int) else value)
576+
index += 1
577+
578+
response = self.sendReceive(addRequest, controls)[0]['protocolOp']
579+
if response['addResponse']['resultCode'] != ResultCode('success'):
580+
raise LDAPSessionError(
581+
error=int(response['addResponse']['resultCode']),
582+
errorString=f"Error in addRequest -> {response['addResponse']['resultCode'].prettyPrint()}: {response['addResponse']['diagnosticMessage']}"
583+
)
584+
return True
585+
586+
def modify(self, dn, modifications, controls=None):
587+
"""
588+
Modify an entry in the LDAP directory.
589+
RFC 4511 Section 4.6
590+
591+
:param dn: Distinguished Name of the entry to modify
592+
:param modifications: Dictionary of modifications of form {attribute: [(operation, values), ...]}.
593+
Operation: 0 - add, 1 - delete, 2 - replace, 3 - increment.
594+
Values: single value or list of values.
595+
:param controls: LDAP controls to include in the request
596+
:return: True if the entry was modified successfully, else raises LDAPSessionError
597+
"""
598+
modifyRequest = ModifyRequest()
599+
modifyRequest['object'] = dn
600+
601+
# idx keeps track of the current change index
602+
idx = 0
603+
for attr, ops in modifications.items():
604+
for op in ops:
605+
# op should be a tuple (operation, values)
606+
if op[0] not in [MODIFY_ADD, MODIFY_DELETE, MODIFY_REPLACE, MODIFY_INCREMENT]:
607+
raise LDAPSessionError(errorString=f"Invalid modification operation '{op[0]}' for attribute '{attr}'")
608+
modifyRequest['changes'][idx]['operation'] = op[0] # operation code
609+
modifyRequest['changes'][idx]['modification']['type'] = attr
610+
611+
# prepare changed values. Integer values need to be converted to string
612+
vals = []
613+
if isinstance(op[1], list):
614+
vals.extend([str(val) if isinstance(val, int) else val for val in op[1]])
615+
else:
616+
vals.append(str(op[1]) if isinstance(op[1], int) else op[1])
617+
modifyRequest['changes'][idx]['modification']['vals'].setComponents(*vals)
618+
idx += 1
619+
620+
response = self.sendReceive(modifyRequest, controls)[0]['protocolOp']
621+
if response['modifyResponse']['resultCode'] != ResultCode('success'):
622+
raise LDAPSessionError(
623+
error=int(response['modifyResponse']['resultCode']),
624+
errorString=f"Error in modifyRequest -> {response['modifyResponse']['resultCode'].prettyPrint()}: {response['modifyResponse']['diagnosticMessage']}"
625+
)
626+
return True
627+
628+
def modify_dn(self, dn, newrdn, deleteoldrdn=True, newSuperior=None, controls=None):
629+
"""
630+
Modify the Distinguished Name of an entry in the LDAP directory.
631+
RFC 4511 Section 4.9
632+
633+
:param dn: Current Distinguished Name of the entry
634+
:param newrdn: New Relative Distinguished Name for the entry
635+
:param deleteoldrdn: Whether to delete the old RDN from the entry
636+
:param newSuperior: New superior DN if moving the entry to a different container
637+
:param controls: LDAP controls to include in the request
638+
:return: True if the DN was modified successfully, else raises LDAPSessionError
639+
"""
640+
modifyDNRequest = ModifyDNRequest()
641+
modifyDNRequest['entry'] = dn
642+
modifyDNRequest['newrdn'] = newrdn
643+
modifyDNRequest['deleteoldrdn'] = deleteoldrdn
644+
if newSuperior is not None:
645+
modifyDNRequest['newSuperior'] = newSuperior
646+
647+
response = self.sendReceive(modifyDNRequest, controls)[0]['protocolOp']
648+
if response['modDNResponse']['resultCode'] != ResultCode('success'):
649+
raise LDAPSessionError(
650+
error=int(response['modDNResponse']['resultCode']),
651+
errorString=f"Error in modifyDNRequest -> {response['modDNResponse']['resultCode'].prettyPrint()}: {response['modDNResponse']['diagnosticMessage']}"
652+
)
653+
return True
654+
655+
def delete(self, dn, controls=None):
656+
"""
657+
Delete an entry from the LDAP directory.
658+
RFC 4511 Section 4.8
659+
660+
:param dn: Distinguished Name of the entry to delete
661+
:param controls: LDAP controls to include in the request
662+
:return: True if the entry was deleted successfully, else raises LDAPSessionError
663+
"""
664+
deleteRequest = DelRequest(dn)
665+
666+
response = self.sendReceive(deleteRequest, controls)[0]['protocolOp']
667+
if response['delResponse']['resultCode'] != ResultCode('success'):
668+
raise LDAPSessionError(
669+
error=int(response['delResponse']['resultCode']),
670+
errorString=f"Error in deleteRequest -> {response['delResponse']['resultCode'].prettyPrint()}: {response['delResponse']['diagnosticMessage']}"
671+
)
672+
return True
673+
549674
def close(self):
550675
if self._socket is not None:
551676
self._socket.close()

0 commit comments

Comments
 (0)