#!/usr/bin/env python


'''
@author:        r4wd3r
@license:       GNU General Public License 2.0 or later
@contact:       r4wd3r@gmail.com
'''

import argparse
import re

import codecs


class User():
    '''
    Class used to store the users and process their info
    '''

    def __init__(self):
        self.distinguishedname = ""
        self.sid = ""
        self.samaccountname = ""
        self.samaccounttype = ""
        self.deleted = False
        self.enabled = True
        self.lastlogon = ""
        self.displayname = ""
        self.givenname = ""
        self.surname = ""
        self.nthash = ""
        self.lmhash = ""
        self.nthistory = []  # Defined as property. It is set wihtout append.
        self.lmhistory = []  # Defined as property. It is set wihtout append.
        self.cleartext = ""
        self.wdigest = []  # Defined as property. It is set wihtout append.

    @property
    def distinguishedname(self):
        return self.distinguishedname

    @distinguishedname.setter
    def distinguishedname(self, value):
        self.distinguishedname = value

    @property
    def sid(self):
        return self.sid

    @sid.setter
    def sid(self, value):
        self.sid = value

    @property
    def samaccountname(self):
        return self.samaccountname

    @samaccountname.setter
    def samaccountname(self, value):
        self.samaccountname = value

    @property
    def samaccounttype(self):
        return self.samaccounttype

    @samaccounttype.setter
    def samaccounttype(self, value):
        self.samaccounttype = value

    @property
    def deleted(self):
        return self.sid

    @deleted.setter
    def deleted(self, value):
        self.deleted = value

    @property
    def enabled(self):
        return self.enabled

    @enabled.setter
    def enabled(self, value):
        self.enabled = value

    @property
    def lastlogon(self):
        return self.lastlogon

    @lastlogon.setter
    def lastlogon(self, value):
        self.lastlogon = value

    @property
    def displayname(self):
        return self.displayname

    @displayname.setter
    def displayname(self, value):
        self.displayname = value

    @property
    def givenname(self):
        return self.givenname

    @givenname.setter
    def givenname(self, value):
        self.givenname = value

    @property
    def surname(self):
        return self.surname

    @surname.setter
    def surname(self, value):
        self.surname = value

    @property
    def nthash(self):
        return self.nthash

    @nthash.setter
    def nthash(self, value):
        self.nthash = value

    @property
    def lmhash(self):
        return self.lmhash

    @lmhash.setter
    def lmhash(self, value):
        self.lmhash = value

    @property
    def cleartext(self):
        return self.cleartext

    @cleartext.setter
    def cleartext(self, value):
        self.cleartext = value

    @property
    def nthistory(self):
        return self.nthistory

    @nthistory.setter
    def nthistory(self, value):
        self.nthistory = value

    @property
    def lmhistory(self):
        return self.lmhistory

    @lmhistory.setter
    def lmhistory(self, value):
        self.lmhistory = value

    @property
    def wdigest(self):
        return self.wdigest

    @wdigest.setter
    def wdigest(self, value):
        self.wdigest = value


def processFile(filename):
    '''
    :param filename: Receives the text file generated by Get-ADReplAccount and process it
    :return: Returns the list of objects "User", based on the class "User" 
    '''

    # TODO: Parse the Kerberos Hashes.

    users_list = []
    current_user = User()
    hash_pattern = re.compile('.*Hash\s[0-9]*.*')

    try:
        f = open(filename, 'rt', encoding='utf-8', errors='replace')
        lines = f.readlines()
        f.close()
    except:
        print (
            'ERROR: Cannot open the file ' + filename + '. Make sure it exists and it\'s readable, at least by a monkey!')
        return None
    for i in xrange(len(lines)):
        # To determine if is found a new user on the file, the string DistinguishedName: is used.
        # When matched, always begins a block of a new user data lines

        if "DistinguishedName:" in lines[i] and current_user.distinguishedname == "":
            _temp = lines[i].split(':')
            current_user.distinguishedname = str(_temp[1]).rstrip()

        elif "DistinguishedName:" in lines[i] and current_user.distinguishedname != "":
            users_list.append(current_user)
            current_user = User()
            _temp = lines[i].split(':')
            current_user.distinguishedname = str(_temp[1]).strip()

        elif "Sid:" in lines[i]:
            _temp = lines[i].split(':')
            current_user.sid = str(_temp[1]).strip()

        elif "SamAccountName:" in lines[i]:
            _temp = lines[i].split(':')
            current_user.samaccountname = str(_temp[1]).strip()

        elif "SamAccountType:" in lines[i]:
            _temp = lines[i].split(':')
            current_user.samaccounttype = str(_temp[1]).strip()

        # Saves enabled as string
        elif "Enabled:" in lines[i]:
            _temp = lines[i].split(':')
            current_user.enabled = str(_temp[1]).strip()

        # Saves deleted as String
        elif "Deleted:" in lines[i]:
            _temp = lines[i].split(':')
            current_user.deleted = str(_temp[1]).strip()

        elif "LastLogon:" in lines[i]:
            _temp = lines[i].split(':')
            current_user.lastlogon = str(_temp[1]).strip()

        elif "DisplayName:" in lines[i]:
            _temp = lines[i].split(':')
            current_user.displayname = str(_temp[1]).strip()

        elif "GivenName:" in lines[i]:
            _temp = lines[i].split(':')
            current_user.givenname = str(_temp[1]).strip()

        elif "Surname:" in lines[i]:
            _temp = lines[i].split(':')
            current_user.surname = str(_temp[1]).strip()

        elif "NTHash:" in lines[i]:
            _temp = lines[i].split(':')
            current_user.nthash = str(_temp[1]).strip()

        elif "LMHash:" in lines[i]:
            _temp = lines[i].split(':')
            current_user.lmhash = str(_temp[1]).strip()

        elif "ClearText:" in lines[i]:
            _temp = lines[i].split(':')
            current_user.cleartext = str(_temp[1]).strip()

        elif "NTHashHistory:" in lines[i]:
            # Creates the list nthistory, and then is assigned to user.nthistory list
            i += 1
            nthistory = []
            while hash_pattern.match(str(lines[i])):
                _temp = lines[i].split(':')
                nthistory.append(str(_temp[1]).strip())
                i += 1
            current_user.nthistory = nthistory

        elif "LMHashHistory:" in lines[i]:
            # Creates the list lmhistory, and then is assigned to user.lmhistory list
            i += 1
            lmhistory = []
            while hash_pattern.match(str(lines[i])):
                _temp = lines[i].split(':')
                lmhistory.append(str(_temp[1]).strip())
                i += 1
            current_user.lmhistory = lmhistory

        elif "WDigest:" in lines[i]:
            # Creates the list wdigest, and then is assigned to user.wdigest list
            i += 1
            wdigest = []
            while hash_pattern.match(str(lines[i])):
                _temp = lines[i].split(':')
                wdigest.append(str(_temp[1]).strip())
                i += 1
            current_user.wdigest = wdigest

    users_list.append(current_user)  # Appends the last user, without using the string DistinguishedName
    print ".........................................................OK"

    return users_list


def writeClearTextFile(users_list, output):
    '''
    :param users_list: List of users created by the method processFile
    :param output: Name of the output file
    :return: Creates the cleartext file (username:password)
    '''
    print "\nCreating ClearTextFile..."
    filename = output + "_cleartext.txt"
    try:
        f = codecs.open(filename, "w", "utf-8")
    except:
        print "ERROR: Cannot create the cleartext file."
        print "Writing user (0/%d)" % (len(users_list)),
    for i in xrange(len(users_list)):
        print "\rWriting user (%d/%d)" % (i + 1, len(users_list)),
        if users_list[i].cleartext != "" and users_list[i].cleartext != " ":
            f.writelines(users_list[i].samaccountname + ":" + users_list[i].cleartext + '\n')
    print ".........................................................OK"
    print "[+] ClearText File: %s successfully created." % (filename)


def writeNTLMFile(users_list, output):
    '''
    :param users_list: List of users created by the method processFile
    :param output: Name of the output file
    :return: Creates the NTLM file (username:password)
    '''
    print "\nCreating NTLM File..."
    filename = output + "_ntlm.txt"
    try:
        f = codecs.open(filename, "w", "utf-8")
    except:
        print "ERROR: Cannot create the cleartext file."

    print "Writing user (0/%d)" % (len(users_list)),
    for i in xrange(len(users_list)):
        print "\rWriting user (%d/%d)" % (i + 1, len(users_list)),
        f.writelines(users_list[i].samaccountname + ":" + users_list[i].nthash + '\n')
    print ".........................................................OK"
    print "[+] NTLM File: %s successfully created." % (filename)


def writeNTLMHistoryFile(users_list, output):
    '''
    :param users_list: List of users created by the method processFile
    :param output: Name of the output file
    :return: Creates the NTLMHistory file (username_nthistory[0-n]:hash)
    '''
    print "\nCreating NTLM History File..."
    filename = output + "_ntlm_history.txt"
    try:
        f = codecs.open(filename, "w", "utf-8")
    except:
        print "ERROR: Cannot create the NTLM History file."

    print "Writing user (0/%d)" % (len(users_list)),
    for i in xrange(len(users_list)):
        print "\rWriting user (%d/%d)" % (i + 1, len(users_list)),
        for j in xrange(len(users_list[i].nthistory)):
            f.writelines(users_list[i].samaccountname + "_nthistory" + str(j) + ":" + users_list[i].nthistory[j] + '\n')
    print ".........................................................OK"
    print "[+] NTLM History File: %s successfully created." % (filename)


def writeLMFile(users_list, output):
    '''
    :param users_list: List of users created by the method processFile
    :param output: Name of the output file
    :return: Creates the NTLM file (username:password)
    '''
    print "\nCreating LM File..."
    filename = output + "_lm.txt"
    try:
        f = codecs.open(filename, "w", "utf-8")
    except:
        print "ERROR: Cannot create the cleartext file."

    print "Writing user (0/%d)" % (len(users_list)),
    for i in xrange(len(users_list)):
        print "\rWriting user (%d/%d)" % (i + 1, len(users_list)),
        if users_list[i].cleartext != "" and users_list[i].cleartext != " ":
            f.writelines(users_list[i].samaccountname + ":" + users_list[i].lmhash + '\n')
    print ".........................................................OK"
    print "[+] LM File: %s successfully created." % (filename)


def writeLMHistoryFile(users_list, output):
    '''
    :param users_list: List of users created by the method processFile
    :param output: Name of the output file
    :return: Creates the LMHistory file (username_lmhistory[0-n]:hash)
    '''
    print "\nCreating LM History File..."
    filename = output + "_lm_history.txt"
    try:
        f = codecs.open(filename, "w", "utf-8")
    except:
        print "ERROR: Cannot create the LM History file."

    print "Writing user (0/%d)" % (len(users_list)),
    for i in xrange(len(users_list)):
        print "\rWriting user (%d/%d)" % (i + 1, len(users_list)),
        for j in xrange(len(users_list[i].lmhistory)):
            f.writelines(users_list[i].samaccountname + "_lmhistory" + str(j) + ":" + users_list[i].lmhistory[j] + '\n')
    print ".........................................................OK"
    print "[+] LM History File: %s successfully created." % (filename)


def writeWDigestFile(users_list, output):
    '''
    :param users_list: List of users created by the method processFile
    :param output: Name of the output file
    :return: Creates the wdigest file (username_wdhistory[0-n]:hash)
    '''
    print "\nCreating WDigest History File..."
    filename = output + "_wdigest.txt"
    try:
        f = codecs.open(filename, "w", "utf-8")
    except:
        print "ERROR: Cannot create the wdigest file."
    print "Writing user (0/%d)" % (len(users_list)),
    for i in xrange(len(users_list)):
        print "\rWriting user (%d/%d)" % (i + 1, len(users_list)),
        for j in xrange(len(users_list[i].wdigest)):
            f.writelines(users_list[i].samaccountname + "_wdhistory" + str(j) + ":" + users_list[i].wdigest[j] + '\n')
    print ".........................................................OK"
    print "[+] WDigest File: %s successfully created." % (filename)


def main():
    print "-----------------------"
    print "DSInternals Parser v1.1"
    print "-----------------------\n"

    parser = argparse.ArgumentParser(
        description="Parses a Get-ADReplAccount generated file to extract credentials data, including hashes.")
    parser.add_argument("input_file", help='File to process, generated by Get-ADReplAccount of DSInternals', type=str)
    parser.add_argument('-o', '--output', help='Prefix name for output files.', type=str, default="output")
    parser.add_argument('--ntlm', help='Generate the file with username and current NTLM hash.', action="store_true")
    parser.add_argument('--nthistory', help='Generate the file with username and history NTLM hashes.',
                        action="store_true")
    parser.add_argument('--lm', help='Generate the file with username and current LM hash.', action="store_true")
    parser.add_argument('--lmhistory', help='Generate the file with username and history LM hashes.',
                        action="store_true")
    parser.add_argument('--cleartext', help='Generate the file with existent users that have ClearText password.',
                        action="store_true")
    parser.add_argument('--wdigest', help='Generate the file with existent users that have Wdigest password.',
                        action="store_true")

    args = parser.parse_args()

    filename = args.input_file
    output = args.output
    print "Reading file...",
    users_list = processFile(filename)

    if not args.ntlm and not args.nthistory and not args.lm and not args.lmhistory and not args.cleartext and not args.wdigest:
        writeNTLMFile(users_list, output)
        writeNTLMHistoryFile(users_list, output)
        writeLMFile(users_list, output)
        writeLMHistoryFile(users_list, output)
        writeClearTextFile(users_list, output)
        writeWDigestFile(users_list, output)

    if args.ntlm:
        writeNTLMFile(users_list, output)
    if args.nthistory:
        writeNTLMHistoryFile(users_list, output)
    if args.lm:
        writeLMFile(users_list, output)
    if args.lmhistory:
        writeLMHistoryFile(users_list, output)
    if args.cleartext:
        writeClearTextFile(users_list, output)
    if args.wdigest:
        writeWDigestFile(users_list, output)


main()
