#!/usr/bin/env python
"""
Read in a ldap/ldif file and split out a unix-formated password file to run in
john the ripper for password auditing.

Typical usage (on the ldap server):
	Dump the ldap database
		slapcat > ldif.out
	Convert it to passwd format
		ldap-passwd-dump.py ldif.out passwd.out
	Run john to crack passwords
		john passwd.out

Aaron Peterson <aaron@midnightresearch.com>
"""

import sys, base64, re, os

class Results:
	"""
	Keep all user results here
	"""
	def __init__(self):
		self.users = []

	def addUser(self, user):
		# Check for duplicates
		dupe = 0
		for u in self.users:
			if u.uid == user.uid:
				dupe = 1
				break
		if not dupe:
			print " [*] Adding new user [%s, %s] to results" % (user.cn, user.uid)
			self.users.append(user)
		else:
			print " [*] Not adding duplicate user [%s]" % user.cn

class User(list):
	def __init__(self, hash=None, base64=None, password=None, cn=None, uid=None):
		self.hash = hash
		self.uid = uid
		self.base64 = base64
		self.password = password
		self.cn = cn
		list.__init__(self)

class  LDIFCrack:
	def main(self):
		# Open file
		f = open(self.ldif, "r")
		# Load in the first user
		user = User()
		isInGroup=0
		# Load lines into a "user"
		for line in f:
			if re.compile(r"^\s*$").search(line):
				# Only append the old user if it's in the right group, and has a password set
				if isInGroup and user.hash:
					self.results.addUser(user)
				# Reset user and counter
				user = User()
				isInGroup=0
			# Make sure we test the right groups
			if re.compile(self.groupMatch).search(line):
				isInGroup=1
			# Pull out the password
			match = re.compile(r"userPassword:: (.*)$").search(line)
			if match:
				user.base64 = match.group(1)
				try:
					user.hash = base64.decodestring(user.base64)
				except:
					print " [!] Could not decode string [%s]" % user.base64
					continue
				# Remove {crypt} from hash string if needed
				match = re.compile("\{.*\}(.*)$").search(user.hash)
				if match:
					user.hash = match.group(1)
			# uid
			match = re.compile(r"uid: (.*)$").search(line)
			if match:
				user.uid= match.group(1)
			# Grab the common name
			matchCn = re.compile(r"cn: (.*)$").search(line)
			if matchCn:
				user.cn = matchCn.group(1)

	def printPasswd(self, file):
		f = open(file, "w")
		for user in self.results.users:
			line = "%s:%s:::%s" % (user.uid, user.hash, user.cn)
			f.write(line + "\n")
			print " [*] %s" % line
		f.close()
		print " [*] Wrote [%s] password lines to [%s] " % (len(self.results.users), file)

	def __init__(self, ldif, groupMatch):
		self.ldif = ldif
		self.results = Results()
		self.groupMatch = groupMatch
		self.main()

if __name__ == "__main__":
	if len(sys.argv) < 3:
		print "\nusage: %s <ldif file> <output password file> [<user matchString>]" % sys.argv[0]
		print "       example: %s ldif.out passwd.txt \"^ou: MyGroup\"" % sys.argv[0]
		print "       (matchString default is \"objectClass: posixAccount\")\n"
		sys.exit(1)

	ldif = sys.argv[1]
	passwdFile = sys.argv[2]
	if not os.path.exists(ldif):
		print " [!] LDIF Input file [%s] does not exist..." % ldif
		sys.exit(1)
	if os.path.exists(passwdFile):
		print " [!] Won't overwrite existing passwd file [%s]" % passwdFile
		sys.exit(1)

	# Will match the user against this group before cracking it if it's set
	if len(sys.argv) == 4:
		groupMatch = sys.argv[3]
	else:
		groupMatch = "objectClass: posixAccount"
	ldifcrack = LDIFCrack(ldif, groupMatch)
	ldifcrack.printPasswd(passwdFile)

	print " [*] Done"
