text: from __future__ import unicode_literals
  import argparse
  import os
  import logging
  import json
  import codecs
  import re
  from ldapdomaindump import trust_flags, trust_directions
  from builtins import str, itervalues, iteritems
  
  logging.basicConfig()
  logger = logging.getLogger('ldd2bloodhound')
  
  class Utils(object):
      @staticmethod
      def ldap_to_domain(ldap):
          return re.sub(',DC=', '.', ldap[ldap.find('DC='):], flags=re.I)[3:]
  
      @staticmethod
      def get_group_object(groupo, domain):
          return {
              'dn': groupo['dn'],
              'sid': groupo['attributes']['objectSid'][0],
              'type': 'group',
              'principal': '%s@%s' % (groupo['attributes']['sAMAccountName'][0].upper(), domain.upper()),
              'memberOf': groupo['attributes']['memberOf'] if 'memberOf' in groupo['attributes'] else []
          }
  
  class BloodHoundConverter(object):
      def __init__(self):
          # Input files
          self.computers_files = []
          self.trust_files = []
          self.group_files = []
          self.user_files = []
  
          # Caches
          self.groups_by_dn = {}
          self.groups_by_sid = {}
          self.domaincache = {}
  
      # Get domain from sid and dn - use cache if possible
      def get_domain(self, sid, dn):
          dsid = sid.rsplit('-', 1)[0]
          try:
              return self.domaincache[dsid]
          except KeyError:
              self.domaincache[dsid] = Utils.ldap_to_domain(dn)
              return self.domaincache[dsid]
  
      def build_mappings(self):
          # Parse groups, build DN and SID index
          for file in self.group_files:
              with codecs.open(file, 'r', 'utf-8') as infile:
                  data = json.load(infile)
              # data is now a list of groups (objects)
              for group in data:
                  groupattrs = Utils.get_group_object(group, self.get_domain(group['attributes']['objectSid'][0], group['dn']))
                  self.groups_by_dn[group['dn']] = groupattrs
                  self.groups_by_sid[group['attributes']['objectSid'][0]] = groupattrs
          return
  
      def write_users(self):
          # Read user mapping - write to csv
          with codecs.open('group_membership.csv', 'w', 'utf-8') as outfile:
              outfile.write('GroupName,AccountName,AccountType\n')
              for file in self.user_files:
                  with codecs.open(file, 'r', 'utf-8') as infile:
                      data = json.load(infile)
                  # data is now a list of users (objects)
                  for user in data:
                      self.write_entry_memberships(user, outfile)
  
      def write_computers(self):
          # Read computer mapping - write to csv
          # file is already created here, we just append
          with codecs.open('group_membership.csv', 'a', 'utf-8') as outfile:
              for file in self.computers_files:
                  with codecs.open(file, 'r', 'utf-8') as infile:
                      data = json.load(infile)
                  # data is now a list of computers (objects)
                  for computer in data:
                      self.write_entry_memberships(computer, outfile, 'computer')
  
      def write_groups(self):
          # Read group mapping - write to csv
          # file is already created here, we just append
          with codecs.open('group_membership.csv', 'a', 'utf-8') as outfile:
              for group in itervalues(self.groups_by_dn):
                  for membergroup in group['memberOf']:
                      try:
                          outfile.write('%s,%s,%s\n' % (self.groups_by_dn[membergroup]['principal'], group['principal'], 'group'))
                      except KeyError:
                          logger.warning('Unknown group %s. Not found in groups cache!', membergroup)
  
      def write_trusts(self):
          direction_map = {flag:meaning.capitalize() for meaning, flag in trust_directions.items()}
          # open output file first
          with codecs.open('trusts.csv', 'w', 'utf-8') as outfile:
              outfile.write('SourceDomain,TargetDomain,TrustDirection,TrustType,Transitive\n')
              for file in self.trust_files:
                  # load the trusts from file
                  with codecs.open(file, 'r', 'utf-8') as infile:
                      data = json.load(infile)
                  # data is now a list of trusts (objects)
                  for trust in data:
                      # process flags similar to BloodHound.py
                      flags = trust['attributes']['trustAttributes'][0]
                      if flags & trust_flags['WITHIN_FOREST']:
                          trustType = 'ParentChild'
                      else:
                          trustType = 'External'
                      if flags & trust_flags['NON_TRANSITIVE']:
                          isTransitive = False
                      else:
                          isTransitive = True
                      out = [
                          Utils.ldap_to_domain(trust['dn']),
                          trust['attributes']['name'][0],
                          direction_map[trust['attributes']['trustDirection'][0]],
                          trustType,
                          str(isTransitive)
                      ]
                      outfile.write(','.join(out) + '\n')
  
      def write_entry_memberships(self, entry, outfile, entry_type='user'):
          domain = self.get_domain(entry['attributes']['objectSid'][0], entry['dn'])
          if entry_type == 'user':
              principal = '%s@%s' % (entry['attributes']['sAMAccountName'][0].upper(), domain.upper())
          else:
              principal = '%s.%s' % (entry['attributes']['sAMAccountName'][0][:-1].upper(), domain.upper())
          if 'memberOf' in entry['attributes']:
              for group in entry['attributes']['memberOf']:
                  try:
                      rgroup = self.groups_by_dn[group]
                      outfile.write('%s,%s,%s\n' % (rgroup['principal'], principal, entry_type))
                  except KeyError:
                      logger.warning('Unknown group %s. Not found in groups cache!', group)
          # Now process primary group id
          dsid = entry['attributes']['objectSid'][0].rsplit('-', 1)[0]
          try:
              rgroup = self.groups_by_sid['%s-%d' % (dsid, entry['attributes']['primaryGroupID'][0])]
              outfile.write('%s,%s,%s\n' % (rgroup['principal'], principal, entry_type))
          except KeyError:
              logger.warning('Unknown rid %d. Not found in groups cache!', entry['attributes']['primaryGroupID'][0])
  
      def parse_files(self, infiles):
          filemap = {
              'domain_users.json': self.user_files,
              'domain_groups.json': self.group_files,
              'domain_trusts.json': self.trust_files,
              'domain_computers.json': self.computers_files,
          }
          for file in infiles:
              # Get the filename
              filename = file.split(os.sep)[-1]
              try:
                  filemap[filename.lower()].append(file)
              except KeyError:
                  logger.debug('Unknown input file: %s', filename)
          return
  
  def ldd2bloodhound():
      parser = argparse.ArgumentParser(description='LDAPDomainDump to BloodHound CSV converter utility. Supports users/computers/trusts conversion.')
  
      #Main parameters
      parser.add_argument("files", type=str, nargs='+', metavar='FILENAME', help="The ldapdomaindump json files to load. Required files: domain_users.json and domain_groups.json")
      parser.add_argument("-d", "--debug", action='store_true', help="Enable debug logger")
  
      args = parser.parse_args()
      if args.debug:
          logger.setLevel(logging.DEBUG)
  
      converter = BloodHoundConverter()
      converter.parse_files(args.files)
      if len(converter.group_files) == 0:
          logger.error('No domain_groups.json files were specified. Need at least one to perform conversion.')
          return
      if len(converter.user_files) == 0:
          logger.error('No domain_users.json files were specified. Need at least one to perform conversion.')
          return
      logger.debug('Mapping groups...')
      converter.build_mappings()
      logger.debug('Processing users')
      converter.write_users()
      logger.debug('Processing groups')
      converter.write_groups()
      logger.debug('Processing computers')
      converter.write_computers()
      logger.debug('Processing trusts')
      converter.write_trusts()
      print('Done!')
  
  if __name__ == '__main__':
      ldd2bloodhound()
  