import re

from impacket.ldap import ldap, ldapasn1
from impacket.ldap.ldap import LDAPSearchError

class CMEModule:
    '''
    Find PKI Enrollment Services in Active Directory and Certificate Templates Names.

    Module by Tobias Neitzel (@qtc_de) and Sam Freeside (@snovvcrash)
    '''
    name = 'adcs'
    description = 'Find PKI Enrollment Services in Active Directory and Certificate Templates Names'
    supported_protocols = ['ldap']
    opsec_safe= True
    multiple_hosts = True

    def options(self, context, module_options):
        '''
        SERVER             PKI Enrollment Server to enumerate templates for. Default is None.
        '''
        self.context = context
        self.regex = re.compile('(https?://.+)')

        self.server = None
        if module_options and 'SERVER' in module_options:
            self.server = module_options['SERVER']

    def on_login(self, context, connection):
        '''
        On a successful LDAP login we perform a search for all PKI Enrollment Server or Certificate Templates Names.
        '''
        if self.server is None:
            search_filter = '(objectClass=pKIEnrollmentService)'
        else:
            search_filter = '(distinguishedName=CN={},CN=Enrollment Services,CN=Public Key Services,CN=Services,CN=Configuration,'.format(self.server)
            self.context.log.highlight('Using PKI Enrollment Server: {}'.format(self.server))

        context.log.debug("Starting LDAP search with search filter '{}'".format(search_filter))

        try:
            sc = ldap.SimplePagedResultsControl()

            if self.server is None:
                resp = connection.ldapConnection.search(searchFilter=search_filter,
                                            attributes=['dNSHostName', 'msPKI-Enrollment-Servers'],
                                            sizeLimit=0, searchControls=[sc],
                                            perRecordCallback=self.process_servers,
                                            searchBase='CN=Configuration,' + connection.ldapConnection._baseDN)
            else:
                resp = connection.ldapConnection.search(searchFilter=search_filter + connection.ldapConnection._baseDN + ')',
                                            attributes=['certificateTemplates'],
                                            sizeLimit=0, searchControls=[sc],
                                            perRecordCallback=self.process_templates,
                                            searchBase='CN=Configuration,' + connection.ldapConnection._baseDN)

        except LDAPSearchError as e:
            context.log.error('Obtained unexpected exception: {}'.format(str(e)))

    def process_servers(self, item):
        '''
        Function that is called to process the items obtain by the LDAP search when listing PKI Enrollment Servers.
        '''
        if not isinstance(item, ldapasn1.SearchResultEntry):
            return

        urls = []
        host_name = None

        try:

            for attribute in item['attributes']:

                if str(attribute['type']) == 'dNSHostName':
                    host_name = attribute['vals'][0].asOctets().decode('utf-8')

                elif str(attribute['type']) == 'msPKI-Enrollment-Servers':

                    values = attribute['vals']

                    for value in values:

                        value = value.asOctets().decode('utf-8')
                        match = self.regex.search(value)

                        if match:
                            urls.append(match.group(1))

        except Exception as e:
            entry = host_name or 'item'
            self.context.log.error("Skipping {}, cannot process LDAP entry due to error: '{}'".format(entry, str(e)))

        if host_name:
            self.context.log.highlight('Found PKI Enrollment Server: {}'.format(host_name))

        for url in urls:
            self.context.log.highlight('Found PKI Enrollment WebService: {}'.format(url))

    def process_templates(self, item):
        '''
        Function that is called to process the items obtain by the LDAP search when listing Certificate Templates Names for a specific PKI Enrollment Server.
        '''
        if not isinstance(item, ldapasn1.SearchResultEntry):
            return

        templates = []
        template_name = None

        try:

            for attribute in item['attributes']:

                if str(attribute['type']) == 'certificateTemplates':
                    for val in attribute['vals']:
                        template_name = val.asOctets().decode('utf-8')
                        templates.append(template_name)

        except Exception as e:
            entry = template_name or 'item'
            self.context.log.error("Skipping {}, cannot process LDAP entry due to error: '{}'".format(entry, str(e)))

        if templates:
            for t in templates:
                self.context.log.highlight('Found Certificate Template: {}'.format(t))

@@ -637,17 +637,29 @@
  is None
-.
+, use CN name
 %0A       
@@ -1379,33 +1379,18 @@
 ing PKI 
-Enrollment Server
+CN
 : %7B%7D'.fo
@@ -1750,49 +1750,8 @@
 es=%5B
-'dNSHostName', 'msPKI-Enrollment-Servers'
 %5D,%0A 
@@ -2937,32 +2937,50 @@
 host_name = None
+%0A        cn = None
 %0A%0A        try:%0A%0A
@@ -2970,33 +2970,32 @@
 e%0A%0A        try:%0A
-%0A
             for 
@@ -3020,32 +3020,50 @@
 'attributes'%5D:%0A%0A
+                %0A%0A
                 
@@ -3185,16 +3185,140 @@
 'utf-8')
+%0A                if str(attribute%5B'type'%5D) == 'cn':%0A                    cn = attribute%5B'vals'%5D%5B0%5D.asOctets().decode('utf-8')
 %0A%0A      
@@ -3992,24 +3992,106 @@
 ost_name))%0A%0A
+        if cn:%0A            self.context.log.highlight('Found CN: %7B%7D'.format(cn))%0A%0A
         for 

Update module adcs