#!/usr/bin/env python
#
import sys
from ldif import LDIFParser, LDIFWriter
import ldap
from ldap.ldapobject import LDAPObject
import binascii
import base64
import re
from optparse import OptionParser, OptionGroup
from getpass import getpass

class MyLDIFParser(LDIFParser):
	def __init__(self, input):
		LDIFParser.__init__(self, input)
		self.objects = {}
		self.parse()
		#print 'Loaded %d objects\n' % len(self.objects)

	def handle(self, dnstr, entry):
		dn = ldap.dn.str2dn(dnstr)
		key = ldap.dn.dn2str(dn)

		try:
			self.objects[key]
		except KeyError, k:
			#print 'KeyError %s\n' % str(k)
			#print 'Adding %s\n' % key
			pass
		else:
		#print 'Exists %s\n' % key
			raise

		self.objects[key] = entry
		return

	def do_dn_match(self, base_dn_str, scope, dn_str):
		b = ldap.dn.str2dn(base_dn_str)
		d = ldap.dn.str2dn(dn_str)
		#print 'base: %s: len: %d\n' % (str(b), len(b))
		#print 'dn: %s: len: %d\n' % (str(d), len(d))
		bi = len(b)
		di = len(d)
		i = 1
		while bi >= i:
			bc = b[bi - i]
			#print 'sp[%d] si[%d] i[%d]: %s\n' % (si - i, si, i, str(s))
			if i > di:
        		    #print 'i:%d > di:%d\n' % (i, di)
			    return False
			dc = d[di - i]
			#print 'dp[%d] di[%d] i[%d]: %s\n' % (di - i, di, i, str(dc))
			i += 1
			if bc != dc:
        		    #print 'i:%d no match %s != %s\n' % (i, str(d), str(s))
			    return False
          #print 'i:%d match %s == %s\n' % (i, str(d), str(s))

		if scope == ldap.SCOPE_BASE:
			if di != bi:
				return False
		elif scope == ldap.SCOPE_ONELEVEL:
			if di != bi + 1:
				return False
		elif scope == ldap.SCOPE_SUBTREE:
			if di < bi:
				return False
		else:
			raise

#if di > i:
      #    print 'i:%d > di:%d\n' % (i, di)
      #    return False
      #print 'r: si[%d] di[%d] i[%d]\n' % (si, di, i)
      #print 'dp[%d] di[%d] i[%d]: %s\n' % (di - i, di, i, str(dn))
      #print 'dp[%d] di[%d] i[%d]: %s\n' % (di - i, di, i, str(dn))
      #print 'i:%d match %s == %s\n' % (i, str(d), str(s))
		return True

	def parse_filter(self, s, start=-1, end=-1):
		f = list()

		if start == -1:
			start = 0

		if end == -1:
			end = len(s);

		#print 'full[%s] s[%s] start[%d], end[%d]\n' % (s, s[start:end], start, end)

		if not s.startswith('(', start, end):
			raise "not ("
		start += 1

		if not s.endswith(')', start, end):
			raise "not )"
		end = s.rfind(')', start, end)

		if s.startswith('&', start, end):
			start += 1
			f.append('AND')

			cur_start = start
			cur_end = cur_start
			open = 0
			for i in range(start, end):
				cur_end += 1
				if s[i] == '(':
					open += 1
					continue
				if s[i] != ')':
					continue

				open -= 1
				if open != 0:
					continue
				f.append(self.parse_filter(s, cur_start, cur_end))
				cur_start = cur_end

			return f

		if s.startswith('|', start, end):
			start += 1
			f.append('OR')

			cur_start = start
			cur_end = cur_start
			open = 0
			for i in range(start, end):
				cur_end += 1
				if s[i] == '(':
					open += 1
					continue
				if s[i] != ')':
					continue

				open -= 1
				if open != 0:
					continue
				f.append(self.parse_filter(s, cur_start, cur_end))
				cur_start = cur_end

			return f

		if s.startswith('!', start, end):
			start += 1
			f.append('NOT')
			f.append(self.parse_filter(s, start, end))
			return f

		if s.find('>=', start, end) != -1:
			ss = s[start:end]
			t = ss.split('>=', 1)
			f.append('GREATER')
			f.append(t)
			return f

		if s.find('<=', start, end) != -1:
			ss = s[start:end]
			t = ss.split('<=', 1)
			f.append('LESS')
			f.append(t)
			return f

		if s.find('=', start, end):
			ss = s[start:end]
			t = ss.split('=', 1)
			f.append('EQUALITY')
			f.append(t)
			return f

		raise "end"

	def val_match(self, stored, searched):
		if searched == '*':
			return True

		if searched == stored:
			return True

		return False

	def do_filter_match(self, filter, entry):
		if filter[0] == 'AND':
			for i in range(1, len(filter)):
				match = self.do_filter_match(filter[i], entry)
				if not match:
					return False
			return True

		if filter[0] == 'OR':
			for i in range(1, len(filter)):
				match = self.do_filter_match(filter[i], entry)
				if match:
					return True
			return False

		if filter[0] == 'NOT':
			match = self.do_filter_match(filter[1], entry)
			if not match:
				return True
			return False

		if filter[0] == 'EQUALITY':
			data = filter[1]
			a = data[0]
			v = data[1]
			try:
				values = entry[a]
				for i in range(0, len(values)):
					if self.val_match(values[i], v):
						return True
			except:
				return False

			return False

		raise "invalid filter[%s]" % filter[0]
		return False

	def search(self, base, scope,
		   filterstr='(objectClass=*)',
		   attrlist=None,
		   attrsonly=0):
		filter = self.parse_filter(filterstr)
		#print "filter[%s]\nparsed[%s]\n" % (filterstr, filter)
		copy = self.objects.copy();
		for dn, entry in copy.items():
			if not self.do_dn_match(base, scope, dn):
				del copy[dn]
				continue
			if not self.do_filter_match(filter, entry):
				del copy[dn]
				continue
			if attrlist is None:
				continue
			#self.strip_attrs(copy[k], attrlist)
		return copy

class MyLDAP(LDAPObject):
	def __init__(self, input_ldif):
		self.ldif = MyLDIFParser(input_ldif)

	def search(self, base, scope,
		   filterstr='(objectClass=*)',
		   attrlist=None,
		   attrsonly=0):
		return self.ldif.search(base, scope, filterstr, attrlist, attrsonly)

#
# we don't want <SID=S-1-5-32-544> as base64...
#
#SAFE_STRING_PATTERN = '(^(\000|\n|\r| |:|<)|[\000\n\r\200-\377]+|[ ]+$)'
SAFE_STRING_PATTERN = '(^(\000|\n|\r| |:)|[\000\n\r\200-\377]+|[ ]+$)'
safe_string_re = re.compile(SAFE_STRING_PATTERN)
class MyLDIFWriter(LDIFWriter):
	def _needs_base64_encoding(self,attr_type,attr_value):
		return self._base64_attrs.has_key(attr_type.lower()) or \
		not safe_string_re.search(attr_value) is None

#
# The dump() function is taken from:
# http://code.activestate.com/recipes/142812/
# and available under PSF License.
# It was posted by Sebastien Keim
#
FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)])
def dump(src, length=8):
	N=0; result=''
	while src:
		s,src = src[:length],src[length:]
		hexa = ''.join(["%02X"%ord(x) for x in s])
		s = s.translate(FILTER)
		result += "%04X   %-*s %s\n" % (N, length*3, hexa, s)
		N+=length
	return result

ACB_DISABLED			= 0x00000001 # 1 = User account disabled
ACB_HOMDIRREQ			= 0x00000002 # 1 = Home directory required
ACB_PWNOTREQ			= 0x00000004 # 1 = User password not required
ACB_TEMPDUP			= 0x00000008 # 1 = Temporary duplicate account
ACB_NORMAL			= 0x00000010 # 1 = Normal user account
ACB_MNS				= 0x00000020 # 1 = MNS logon user account
ACB_DOMTRUST			= 0x00000040 # 1 = Interdomain trust account
ACB_WSTRUST			= 0x00000080 # 1 = Workstation trust account
ACB_SVRTRUST			= 0x00000100 # 1 = Server trust account
ACB_PWNOEXP			= 0x00000200 # 1 = User password does not expire
ACB_AUTOLOCK			= 0x00000400 # 1 = Account auto locked
ACB_ENC_TXT_PWD_ALLOWED		= 0x00000800 # 1 = Encryped text password is allowed
ACB_SMARTCARD_REQUIRED		= 0x00001000 # 1 = Smart Card required
ACB_TRUSTED_FOR_DELEGATION	= 0x00002000 # 1 = Trusted for Delegation
ACB_NOT_DELEGATED		= 0x00004000 # 1 = Not delegated
ACB_USE_DES_KEY_ONLY		= 0x00008000 # 1 = Use DES key only
ACB_DONT_REQUIRE_PREAUTH	= 0x00010000 # 1 = Preauth not required
ACB_PW_EXPIRED                  = 0x00020000 # 1 = Password Expired
ACB_NO_AUTH_DATA_REQD		= 0x00080000 # 1 = No authorization data required

# User flags for "userAccountControl"
UF_SCRIPT	 			= 0x00000001
UF_ACCOUNTDISABLE			= 0x00000002
UF_00000004				= 0x00000004
UF_HOMEDIR_REQUIRED			= 0x00000008

UF_LOCKOUT				= 0x00000010
UF_PASSWD_NOTREQD			= 0x00000020
UF_PASSWD_CANT_CHANGE			= 0x00000040
UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED	= 0x00000080

UF_TEMP_DUPLICATE_ACCOUNT		= 0x00000100
UF_NORMAL_ACCOUNT			= 0x00000200
UF_00000400				= 0x00000400
UF_INTERDOMAIN_TRUST_ACCOUNT		= 0x00000800

UF_WORKSTATION_TRUST_ACCOUNT		= 0x00001000
UF_SERVER_TRUST_ACCOUNT			= 0x00002000
UF_00004000				= 0x00004000
UF_00008000				= 0x00008000

UF_DONT_EXPIRE_PASSWD			= 0x00010000
UF_MNS_LOGON_ACCOUNT			= 0x00020000
UF_SMARTCARD_REQUIRED			= 0x00040000
UF_TRUSTED_FOR_DELEGATION		= 0x00080000

UF_NOT_DELEGATED			= 0x00100000
UF_USE_DES_KEY_ONLY			= 0x00200000
UF_DONT_REQUIRE_PREAUTH			= 0x00400000
UF_PASSWORD_EXPIRED			= 0x00800000

UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION= 0x01000000
UF_NO_AUTH_DATA_REQUIRED		= 0x02000000

# Group flags for "groupType"
GROUP_TYPE_BUILTIN_LOCAL_GROUP		= 0x00000001
GROUP_TYPE_ACCOUNT_GROUP		= 0x00000002
GROUP_TYPE_RESOURCE_GROUP		= 0x00000004
GROUP_TYPE_UNIVERSAL_GROUP		= 0x00000008
GROUP_TYPE_APP_BASIC_GROUP		= 0x00000010
GROUP_TYPE_APP_QUERY_GROUP		= 0x00000020
GROUP_TYPE_SECURITY_ENABLED		= 0x80000000

# 0x80000005 -2147483643
GTYPE_SECURITY_BUILTIN_LOCAL_GROUP = \
	GROUP_TYPE_BUILTIN_LOCAL_GROUP | \
	GROUP_TYPE_RESOURCE_GROUP | \
	GROUP_TYPE_SECURITY_ENABLED \

# 0x80000004 -2147483644
GTYPE_SECURITY_DOMAIN_LOCAL_GROUP = \
	GROUP_TYPE_RESOURCE_GROUP | \
	GROUP_TYPE_SECURITY_ENABLED

# 0x80000002 -2147483646
GTYPE_SECURITY_GLOBAL_GROUP = \
	GROUP_TYPE_ACCOUNT_GROUP | \
	GROUP_TYPE_SECURITY_ENABLED

# 0x80000008 -2147483640
GTYPE_SECURITY_UNIVERSAL_GROUP = \
	GROUP_TYPE_UNIVERSAL_GROUP | \
	GROUP_TYPE_SECURITY_ENABLED

GTYPE_DISTRIBUTION_GLOBAL_GROUP		= 0x00000002
GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP	= 0x00000004
GTYPE_DISTRIBUTION_UNIVERSAL_GROUP	= 0x00000008

# Account flags for "sAMAccountType"
ATYPE_NORMAL_ACCOUNT		= 0x30000000 # 805306368
ATYPE_WORKSTATION_TRUST		= 0x30000001 # 805306369
ATYPE_INTERDOMAIN_TRUST		= 0x30000002 # 805306370
ATYPE_SECURITY_GLOBAL_GROUP	= 0x10000000 # 268435456
ATYPE_SECURITY_LOCAL_GROUP	= 0x20000000 # 536870912
ATYPE_DISTRIBUTION_GLOBAL_GROUP	= 0x10000001 # 268435457
ATYPE_DISTRIBUTION_LOCAL_GROUP	= 0x20000001 # 536870913

SID_NAME_USE_NONE = 0 #NOTUSED
SID_NAME_USER     = 1 #user
SID_NAME_DOM_GRP  = 2 #domain group
SID_NAME_DOMAIN   = 3 #domain: don't know what this is
SID_NAME_ALIAS    = 4 #local group
SID_NAME_WKN_GRP  = 5 #well-known group
SID_NAME_DELETED  = 6 #deleted account: needed for c2 rating
SID_NAME_INVALID  = 7 #invalid account
SID_NAME_UNKNOWN  = 8 #oops.
SID_NAME_COMPUTER = 9 #machine

IMPORT_TYPE_USERS     = 1
IMPORT_TYPE_COMPUTERS = 2
IMPORT_TYPE_TRUSTS    = 4
IMPORT_TYPE_GROUPS    = 8
IMPORT_TYPE_ALL       = 11 # no trusts for now

import_types    = dict({"Users": IMPORT_TYPE_USERS, "Computers": IMPORT_TYPE_COMPUTERS, "Trusts": IMPORT_TYPE_TRUSTS, "Groups": IMPORT_TYPE_GROUPS, "All": IMPORT_TYPE_ALL})
env_var_mapping = dict({"%USERNAME%": "sAMAccountName"})

class ImportSamba3SearchState:
	def __init__(self, basedn, scope, filterstr, importou, importdn):
		self.basedn = basedn
		self.scope = scope
		self.filterstr = filterstr
                self.importou = importou
		self.importdn = importdn
		self.results = None


class ImportSamba3LDIF:
	def __init__(self,
			ldap,
			domain_search,
			trusts_search,
			computers_search,
			users_search,
			groups_search,
                        output_ignore_sids=False,
                        remove_input_attributes=[],
                        remove_output_attributes=[],
                        import_type=IMPORT_TYPE_ALL,
                        user_replace_attrs={},
                        computer_replace_attrs={},
			user_principal_name=False):

		self.domain = domain_search
		self.trusts = trusts_search
		self.computers = computers_search
		self.users = users_search
		self.groups = groups_search

                self.user_principal_name = user_principal_name

		self.ldap = ldap

		self.builtin_sid = 'S-1-5-32'

		self.new_DNs = {}
		self.new_lower_DNs = {}
		self.new_SIDs = {}
		self.new_highest_rid = 0
		self.new_lowest_rid = 0xFFFFFFFF
		self.new_sAMAccountNames = {}
		self.new_operations = []

		# TODO
		self.new_next_rid = 140000

		self.convertObjects(remove_input_attributes=remove_input_attributes,
                                    remove_output_attributes=remove_output_attributes,
                                    output_ignore_sids=output_ignore_sids,
                                    import_type=import_type,
                                    user_replace_attrs=user_replace_attrs,
                                    computer_replace_attrs=computer_replace_attrs)

	def parse_sambaAcctFlags(self, str):
		#print "sambaAcctFlags '%s'\n" % str
		s = str.replace(' ', '')
		if not s.startswith('['):
			print "sambaAcctFlags '%s'\n" % str
			raise "sambaAcctFlags invalid "
		if not s.endswith(']'):
			print "sambaAcctFlags '%s'\n" % str
			raise "sambaAcctFlags invalid "

		val = 0;

		for i in range(1, len(s)-1):
			c = s[i]
			if c == 'N':
				val |= ACB_PWNOTREQ
				continue
			if c == 'D':
				val |= ACB_DISABLED
				continue
			if c == 'H':
				val |= ACB_HOMDIRREQ
				continue
			if c == 'T':
				val |= ACB_TEMPDUP
				continue
			if c == 'U':
				val |= ACB_NORMAL
				continue
			if c == 'M':
				val |= ACB_MNS
				continue
			if c == 'W':
				val |= ACB_WSTRUST
				continue
			if c == 'S':
				val |= ACB_SVRTRUST
				continue
			if c == 'L':
				val |= ACB_AUTOLOCK
				continue
			if c == 'X':
				val |= ACB_PWNOEXP
				continue
			if c == 'I':
				val |= ACB_DOMTRUST
				continue
			print "sambaAcctFlags %c '%s'\n" % (c, str)
			raise "sambaAcctFlags invalid "

		return val

	def sambaAcctFlags_to_userAccountControl(self, saf):
		uac = 0

		if not saf:
			return None

		if saf & ACB_DISABLED:
			uac |= UF_ACCOUNTDISABLE

		if saf & ACB_HOMDIRREQ:
			uac |= UF_HOMEDIR_REQUIRED

		if saf & ACB_PWNOTREQ:
			uac |= UF_PASSWD_NOTREQD

		if saf & ACB_TEMPDUP:
			uac |= UF_TEMP_DUPLICATE_ACCOUNT

		if saf & ACB_NORMAL:
			uac |= UF_NORMAL_ACCOUNT

		if saf & ACB_MNS:
			uac |= UF_MNS_LOGON_ACCOUNT

		if saf & ACB_DOMTRUST:
			uac |= UF_INTERDOMAIN_TRUST_ACCOUNT

		if saf & ACB_WSTRUST:
			uac |= UF_WORKSTATION_TRUST_ACCOUNT

		if saf & ACB_SVRTRUST:
			uac |= UF_SERVER_TRUST_ACCOUNT

		if saf & ACB_PWNOEXP:
			uac |= UF_DONT_EXPIRE_PASSWD

		if saf & ACB_AUTOLOCK:
			uac |= UF_LOCKOUT

		if saf & ACB_ENC_TXT_PWD_ALLOWED:
			uac |= UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED

		if saf & ACB_SMARTCARD_REQUIRED:
			uac |= UF_SMARTCARD_REQUIRED

		if saf & ACB_TRUSTED_FOR_DELEGATION:
			uac |= UF_TRUSTED_FOR_DELEGATION

		if saf & ACB_NOT_DELEGATED:
			uac |= UF_NOT_DELEGATED

		if saf & ACB_USE_DES_KEY_ONLY:
			uac |= UF_USE_DES_KEY_ONLY

		if saf & ACB_DONT_REQUIRE_PREAUTH:
			uac |= UF_DONT_REQUIRE_PREAUTH

		if saf & ACB_PW_EXPIRED:
			uac |= UF_PASSWORD_EXPIRED

		if saf & ACB_NO_AUTH_DATA_REQD:
			uac |= UF_NO_AUTH_DATA_REQUIRED

		uac_str = "%d" % uac

		return uac_str

	def sambaAcctFlags_to_sAMAccountType(self, saf):
		sat = 0

		if not saf:
			return None

		if saf & ACB_DOMTRUST:
			assert sat == 0, "sat[0x%08X] saf[0x%08X]" % (sat, saf)
			sat |= ATYPE_INTERDOMAIN_TRUST

		if saf & ACB_WSTRUST:
			assert sat == 0, "sat[0x%08X] saf[0x%08X]" % (sat, saf)
			sat |= ATYPE_WORKSTATION_TRUST

		if saf & ACB_SVRTRUST:
			assert sat == 0, "sat[0x%08X] saf[0x%08X]" % (sat, saf)
			sat |= ATYPE_WORKSTATION_TRUST

		if saf & ACB_NORMAL:
			assert sat == 0, "sat[0x%08X] saf[0x%08X]" % (sat, saf)
			sat |= ATYPE_NORMAL_ACCOUNT

		assert sat != 0, "sat[0x%08X] saf[0x%08X]" % (sat, saf)

		sat_str = "%d" % sat

		return sat_str

	def sambaGroupType_to_groupType(self, sgt_str):
		gt = 0

		sgt = int(sgt_str)

		if sgt == SID_NAME_DOM_GRP:
			gt = GTYPE_SECURITY_GLOBAL_GROUP
		elif sgt == SID_NAME_ALIAS:
			#gt = GTYPE_SECURITY_BUILTIN_LOCAL_GROUP
			gt = GTYPE_SECURITY_DOMAIN_LOCAL_GROUP
		else:
			assert False, "sgt[%r]" % (sgt)

		gt_str = "%d" % gt

		return gt_str

	def unix_to_nttime(self, ut_str):
		fix_up = 11644473600
		nt = 0

		if ut_str is None:
			return None

		ut = int(ut_str)

		if ut == -1:
			nt = 0xFFFFFFFFFFFFFFFFL
		elif ut == 0:
			nt = 0L
		else:
			nt = ut
			nt += fix_up
			nt *= (1000*1000*10)

		return nt

	def sambaBadPasswordCount_to_badPwdCount(self, sbpc):
		bpc = 0

		if not sbpc:
			return None

		bpc_str = "%d" % bpc

		return bpc_str

	def sambaBadPasswordTime_to_badPasswordTime(self, sbpt):
		bpt = 0

		if not sbpt:
			return None

		bpt = self.unix_to_nttime(sbpt)

		bpt_str = "%d" % bpt

		return bpt_str

	def sambaPwdLastSet_to_pwdLastSet(self, spls):
		pls = 0

		if not spls:
			return None

		pls = self.unix_to_nttime(spls)

		pls_str = "%d" % pls

		return pls_str

	def sambaPrimaryGroupSID_to_primaryGroupID(self, spgs, is_computer=False):
		pgid = 0

		if is_computer:
			return '515'

		if not spgs:
			return '513'

		pos = spgs.rfind('-')

		pgid_str = spgs[pos+1:len(spgs)]
		pgid = int(pgid_str)

		pgid_str = "%d" % pgid

		return pgid_str

	def SID_split(self, full):
		dom = ''
		rid = 0

		pos = full.rfind('-')

		rid_str = full[pos+1:len(full)]
		rid = int(rid_str)

		dom = full[0:pos]

		return dom, rid

	def sambaSID_to_objectSid(self, ssid):
		osid = None

		osid = ssid

		return osid

	def sambaLMPassword_to_dBCSPwd(self, sLMp):
		dpwd = None

		if not sLMp:
			return None

		dpwd = binascii.a2b_hex(sLMp)

		return dpwd

	def sambaNTPassword_to_unicodePwd(self, sNTp):
		upwd = None

		#given = 'BXHUE+3ky+6mqz7RwaC4fg=='
		#return base64.b64decode(given)

		if not sNTp:
			return None

		upwd = binascii.a2b_hex(sNTp)

		return upwd

	def sambaPasswordHistory_to_ntPwdHistory(self, sNTb, sPWh):
		nPWb = None
		zeros = '\x00' * 16

		# Note:
		#
		# Each history entry is 32 bytes long.
		# The entry is only useful if the first
		# 16 bytes are all zeros. It means the
		# 2nd 16 bytes represent the raw nthash.
		#
		# The first entry in the history is always
		# the same as the current nthash. That's
		# why we return it in case the sambaPasswordHistory
		# isn't given.

		if not sPWh:
			return sNTb

		sPWb = binascii.a2b_hex(sPWh)
		count = len(sPWb) / 32

		assert (len(sPWb) % 32) == 0, "len in binary[0x%08X]" % (len(sPWb))

		if sPWb[0:16] != zeros:
			return sNTb
		if sPWb[16:32] == zeros:
			return sNTb

		nPWb = sPWb[16:32]

		for i in range(1, count):
			if sPWb[i*32+0:i*32+16] != zeros:
				break
			if sPWb[i*32+16:i*32+32] == zeros:
				break
			#print dump(sPWb, 16)
			#assert True == False, "zeros at i:%u %r\n%r" %(i, sPWb, sPWb[i*32+0:i+32+32])
			nPWb += sPWb[i*32+16:i*32+32]

		#print dump(sPWb, 16)
		#print dump(nPWb, 16)
		return nPWb

	def sambaMungedDial_to_userParameters(self, sMDb64):
		uPb = None

		if not sMDb64:
			return None

		uPb = base64.b64decode(sMDb64)

		#print dump(uPb, 16)
		assert (len(uPb) % 2) == 0, "userParameters not 2 byte alligned len=%u\n" % (len(uPb))

		return uPb

	def sambaLogonHours_to_logonHours(self, sLHh):
		lHb = None

		if not sLHh:
			return None

		lHb = binascii.a2b_hex(sLHh)

		return lHb

	def sambaLogonScript_to_scriptPath(self, sLSs):
		sPs = None

		sPs = sLSs

		return sPs

	def sambaProfilePath_to_profilePath(self, sPPs):
		pPs = None

		pPs = sPPs

		return sPPs

	def sambaHomeDrive_to_homeDrive(self, sHDs):
		hDs = None

		hDs = sHDs

		return hDs

	def sambaHomePath_to_homeDirectory(self, sHPs):
		hDs = None

		hDs = sHPs

		return hDs

	def homeDirectory_to_unixHomeDirectory(self, hDs):
		uHDs = None

		uHDs = hDs

		return uHDs

	def userPassword_to_unixUserPassword(self, uPs):
		uUPs = None

		uUPs = uPs

		return uUPs

	def name_to_sAMAccountName(self, name):
		san = None

		if name is None:
			return None

		san = name.replace('/', '_')

		if san != name:
			print "# sAMAccountName fixes [%s] => [%s]\n" % (name, san)

		return san

	# all attributes
	#
	# cn
	# credentialLevel
	# defaultSearchBase
	# defaultSearchScope
	# defaultServerList
	# description
	# displayName
	# dn
	# employeeNumber
	# employeeType
	# facsimileTelephoneNumber
	# gecos
	# gidNumber
	# givenName
	# homeDirectory
	# houseIdentifier
	# ipHostNumber
	# ipServicePort
	# ipServiceProtocol
	# l
	# loginShell
	# mail
	# memberUid
	# mobile
	# nisDomain
	# nisMapEntry
	# nisMapName
	# o
	# objectClass
	# ou
	# personalTitle
	# roomNumber
	# sambaAcctFlags
	# sambaAlgorithmicRidBase
	# sambaBadPasswordCount
	# sambaBadPasswordTime
	# sambaDomainName
	# sambaForceLogoff
	# sambaGroupType
	# sambaHomeDrive
	# sambaHomePath
	# sambaKickoffTime
	# sambaLMPassword
	# sambaLockoutDuration
	# sambaLockoutObservationWindow
	# sambaLockoutThreshold
	# sambaLogoffTime
	# sambaLogonHours
	# sambaLogonScript
	# sambaLogonTime
	# sambaLogonToChgPwd
	# sambaMaxPwdAge
	# sambaMinPwdAge
	# sambaMinPwdLength
	# sambaMungedDial
	# sambaNextGroupRid
	# sambaNextRid
	# sambaNextUserRid
	# sambaNTPassword
	# sambaPasswordHistory
	# sambaPrimaryGroupSID
	# sambaProfilePath
	# sambaPwdCanChange
	# sambaPwdHistoryLength
	# sambaPwdLastSet
	# sambaPwdMustChange
	# sambaRefuseMachinePwdChange
	# sambaSID
	# sambaSIDList
	# sambaUserWorkstations
	# serialNumber
	# serviceAuthenticationMethod
	# serviceSearchDescriptor
	# shadowLastChange
	# sn
	# telephoneNumber
	# uid
	# uidNumber
	# uniqueMember
	# userPassword

	# keep attributes
	#
	keep_attrs= {}
	keep_attrs['description'] = True
	keep_attrs['displayName'] = True
	keep_attrs['employeeNumber'] = True
	keep_attrs['employeeType'] = True
	keep_attrs['facsimileTelephoneNumber'] = True
	keep_attrs['gecos'] = True
	keep_attrs['gidNumber'] = True
	keep_attrs['givenName'] = True
	keep_attrs['houseIdentifier'] = True
	#keep_attrs['ipHostNumber'] = True
	#keep_attrs['ipServicePort'] = True
	#keep_attrs['ipServiceProtocol'] = True
	keep_attrs['l'] = True
	keep_attrs['loginShell'] = True
	keep_attrs['mail'] = True
	#keep_attrs['memberUid'] = True
	keep_attrs['mobile'] = True
	#keep_attrs['nisDomain'] = True
	#keep_attrs['nisMapEntry'] = True
	#keep_attrs['nisMapName'] = True
	keep_attrs['o'] = True
	keep_attrs['ou'] = True
	keep_attrs['personalTitle'] = True
	keep_attrs['roomNumber'] = True
	keep_attrs['serialNumber'] = True
	#keep_attrs['shadowLastChange'] = True
	keep_attrs['sn'] = True
	keep_attrs['telephoneNumber'] = True
	keep_attrs['uid'] = True
	keep_attrs['uidNumber'] = True
	#keep_attrs['uniqueMember'] = True

	# remove attributes
	#
	remove_attrs = {}
	remove_attrs['cn'] = True
	remove_attrs['credentialLevel'] = True
	remove_attrs['defaultSearchBase'] = True
	remove_attrs['defaultSearchScope'] = True
	remove_attrs['defaultServerList'] = True
	remove_attrs['dn'] = True
	remove_attrs['homeDirectory'] = True
	remove_attrs['memberUid'] = True
	remove_attrs['nisDomain'] = True
	remove_attrs['nisMapEntry'] = True
	remove_attrs['nisMapName'] = True
	remove_attrs['objectClass'] = True
	#remove_attrs['ou'] = True
	remove_attrs['sambaAcctFlags'] = True
	remove_attrs['sambaAlgorithmicRidBase'] = True
	remove_attrs['sambaBadPasswordCount'] = True
	remove_attrs['sambaBadPasswordTime'] = True
	remove_attrs['sambaDomainName'] = True
	remove_attrs['sambaForceLogoff'] = True
	remove_attrs['sambaGroupType'] = True
	remove_attrs['sambaHomeDrive'] = True
	remove_attrs['sambaHomePath'] = True
	remove_attrs['sambaKickoffTime'] = True
	remove_attrs['sambaLMPassword'] = True
	remove_attrs['sambaLockoutDuration'] = True
	remove_attrs['sambaLockoutObservationWindow'] = True
	remove_attrs['sambaLockoutThreshold'] = True
	remove_attrs['sambaLogoffTime'] = True
	remove_attrs['sambaLogonHours'] = True
	remove_attrs['sambaLogonScript'] = True
	remove_attrs['sambaLogonTime'] = True
	remove_attrs['sambaLogonToChgPwd'] = True
	remove_attrs['sambaMaxPwdAge'] = True
	remove_attrs['sambaMinPwdAge'] = True
	remove_attrs['sambaMinPwdLength'] = True
	remove_attrs['sambaMungedDial'] = True
	remove_attrs['sambaNextGroupRid'] = True
	remove_attrs['sambaNextRid'] = True
	remove_attrs['sambaNextUserRid'] = True
	remove_attrs['sambaNTPassword'] = True
	remove_attrs['sambaPasswordHistory'] = True
	remove_attrs['sambaPrimaryGroupSID'] = True
	remove_attrs['sambaProfilePath'] = True
	remove_attrs['sambaPwdCanChange'] = True
	remove_attrs['sambaPwdHistoryLength'] = True
	remove_attrs['sambaPwdLastSet'] = True
	remove_attrs['sambaPwdMustChange'] = True
	remove_attrs['sambaRefuseMachinePwdChange'] = True
	remove_attrs['sambaSID'] = True
	remove_attrs['sambaSIDList'] = True
	remove_attrs['sambaUserWorkstations'] = True
	remove_attrs['serviceAuthenticationMethod'] = True
	remove_attrs['serviceSearchDescriptor'] = True
	remove_attrs['shadowLastChange'] = True
	remove_attrs['userPassword'] = True

	def insert_Operation(self, type, dn, args):
		if type == 'add':
			entry = args
			args = []
			for attr, val in entry.items():
				t = (attr, val)
				args.append(t)

		t = (type, dn, args)
		self.new_operations.append(t)

	def insert_sAMAccountName(self, name, dn):
		name_lower = name.lower()
		assert name_lower not in self.new_sAMAccountNames, \
			"sAMAccountName[%s] already exists as %s\n" \
			% (name, self.new_sAMAccountNames[name_lower])
		self.new_sAMAccountNames[name_lower] = dn

	def get_DN_by_sAMAccountName(self, name):
		name_lower = name.lower()
		#if name_lower not in self.new_sAMAccountNames:
		#	print "%s\n" % (name)
		#	return "TODO"
		assert name_lower in self.new_sAMAccountNames, \
			"sAMAccountName[%s] doesn't exists.\n" \
			% (name)
		return self.new_sAMAccountNames[name_lower]

	def insert_objectSid(self, sid, dn, domain=False, builtin=False, wellknown_rid=False):
		if sid is None:
			return

		assert sid not in self.new_SIDs, \
			'SID[%s] already exists as %s\n' \
			% (sid, self.new_SIDs[sid])

		dom, rid = self.SID_split(sid)

		if domain:
			assert dom == self.domain_sid, \
				"sid[%s] doesn't belong to domain[%s]" \
				% (sid, self.domain_sid)
		elif builtin:
			assert dom == self.builtin_sid, \
				"sid[%s] doesn't belong to " \
				% (sid, self.builtin_sid)
		else:
			assert dom == self.domain_sid or dom == self.builtin_sid, \
				"sid[%s] doesn't belong to domain[%s] nor builtin[%s]" \
				% (sid, self.domain_sid, self.builtin_sid)

		if wellknown_rid:
			assert rid < 1000, "wellknown sid[%s] rid >= 1000" % (sid)
		else:
			assert rid >= 1000, "sid[%s] rid < 1000" % (sid)
			assert rid < 1000000000, "sid[%s] rid >= 1000000000" % (sid)
			if rid < self.new_lowest_rid:
				self.new_lowest_rid = rid
			if rid > self.new_highest_rid:
				self.new_highest_rid = rid

		self.new_SIDs[sid] = dn

	def get_DN_by_SID(self, sid):
		if sid not in self.new_SIDs:
			dom, rid = self.SID_split(sid)
			assert dom == self.domain_sid, \
				"sid[%s] doesn't belong to domain[%s]" \
				% (sid, self.domain_sid)
			assert rid < 1000, "unknown sid[%s] rid >= 1000" % (sid)
			dn = "<SID=%s>" % sid
			return dn

		assert sid in self.new_SIDs, \
			"SID[%s] doesn't exists.\n" \
			% (sid)

		return self.new_SIDs[sid]

	def insert_DN(self, dn, entry):
		dn_lower = dn.lower()
		assert dn_lower not in self.new_lower_DNs, \
			'dn[%s] already exists as [%s]:\n%s\n' \
			% (dn, self.new_lower_DNs[dn_lower], \
			self.new_DNs[self.new_lower_DNs[dn_lower]])

		self.new_lower_DNs[dn_lower] = dn
		self.new_DNs[dn] = entry

	def lookup_DN(self, dn):
		dn_lower = dn.lower()
		if dn_lower not in self.new_lower_DNs:
			return None
		dn = self.new_lower_DNs[dn_lower]
		entry = self.new_DNs[dn]
		return dn, entry

	def insert_Container(self, dn, description=None, displayName=None):
		new = {}
		new['objectClass'] = ['container']
		if description:
			new['description'] = [description]
		if displayName:
			new['displayName'] = [displayName]

		entry = {}
		for attr, val in new.items():
			assert attr not in entry, \
				'attribute[%s] already exists in entry[%s]\n' \
				% (attr, entry[attr])

			if len(val) == 0 or val[0] == None:
				continue

			entry[attr] = val

		self.insert_DN(dn, entry)
		self.insert_Operation('add', dn, entry)

		#print 'old:\n%s\nnew:\n%s\nentry:\n%s' % (old, new, entry)
		return dn, entry

	def insert_organizationalUnit(self, dn, description=None, displayName=None):
		new = {}
		new['objectClass'] = ['organizationalUnit']
		if description:
			new['description'] = [description]
		if displayName:
			new['displayName'] = [displayName]

		entry = {}
		for attr, val in new.items():
			assert attr not in entry, \
				'attribute[%s] already exists in entry[%s]\n' \
				% (attr, entry[attr])

			if len(val) == 0 or val[0] == None:
				continue

			entry[attr] = val

		self.insert_DN(dn, entry)
		self.insert_Operation('add', dn, entry)

		#print 'old:\n%s\nnew:\n%s\nentry:\n%s' % (old, new, entry)
		return dn, entry

	def convert_sambaSamAccount(self, old, baseou, basedn, objectClass="user",
				    keep_sid=True, is_computer=False,
                                    remove_input_attributes=[],
                                    remove_output_attributes=[],
                                    output_ignore_sids=False,
                                    output_display=True,
                                    replace_attrs={},
                                    disable_if_no_unicodePwd=False):

                if objectClass == 'computer':
                    name = old['uid'][0].rstrip('$').upper()
                else:
                    name = old['uid'][0]
		#try:
		#	ou = old['ou'][0]
		#except:
		#	ou = 'DefaultOU'
		#	pass

		ou_dn = "%s,%s" % (baseou, basedn)
		if self.lookup_DN(ou_dn) is None:
			self.insert_organizationalUnit(ou_dn)

		dn = "CN=%s,%s" % (name, ou_dn)

                #lets check our remove list and override/set the course of action
                for attr in remove_input_attributes:
                    if attr in old:
                        del old[attr]

		try:
			sambaAcctFlags = self.parse_sambaAcctFlags(old['sambaAcctFlags'][0])
		except:
			sambaAcctFlags = None
			pass

		try:
			sambaBadPasswordCount = old['sambaBadPasswordCount'][0]
		except:
			sambaBadPasswordCount = None
			pass

		try:
			sambaBadPasswordTime = old['sambaBadPasswordTime'][0]
		except:
			sambaBadPasswordTime = None
			pass

		try:
			sambaPwdLastSet = old['sambaPwdLastSet'][0]
		except:
			sambaPwdLastSet = None
			pass

		try:
			sambaSID = old['sambaSID'][0]
		except:
			sambaSID = None
			pass

		try:
			sambaPrimaryGroupSID = old['sambaPrimaryGroupSID'][0]
		except:
			sambaPrimaryGroupSID = None
			pass

		try:
			sambaLMPassword = old['sambaLMPassword'][0]
		except:
			sambaLMPassword = None
			pass

		try:
			sambaNTPassword = old['sambaNTPassword'][0]
		except:
			sambaNTPassword = None
			pass

		try:
			sambaPasswordHistory = old['sambaPasswordHistory'][0]
		except:
			sambaPasswordHistory = None
			pass

		try:
			sambaLogonScript = old['sambaLogonScript'][0]
		except:
			sambaLogonScript = None

		try:
			sambaProfilePath = old['sambaProfilePath'][0]
		except:
			sambaProfilePath = None
			pass

		try:
			sambaHomeDrive = old['sambaHomeDrive'][0]
		except:
			sambaHomeDrive = None
			pass

		try:
			sambaHomePath = old['sambaHomePath'][0]
		except:
			sambaHomePath = None
			pass

		try:
			sambaLogonHours = old['sambaLogonHours'][0]
		except:
			sambaLogonHours = None
			pass

		try:
			sambaMungedDial = old['sambaMungedDial'][0]
		except:
			sambaMungedDial = None
			pass

		try:
			homeDirectory = old['homeDirectory'][0]
		except:
			homeDirectory = None
			pass

		try:
			userPassword = old['userPassword'][0]
		except:
			userPassword = None
			pass

		if not keep_sid and sambaSID is not None:
			dom, rid = self.SID_split(sambaSID)
			assert dom == self.domain_sid, "dom[%s] self[%s]\n" % (dom, self.domain_sid)
			if rid < 1000:
				#TODO
				newSID = "%s-%s" % (dom, self.new_next_rid)
				self.new_next_rid += 1
				print '# fix SID[%s => %s] for DN[%s]\n' % (sambaSID, newSID, dn)
				sambaSID = newSID
				#print '# skip SID[%s] for DN[%s]\n' % (sambaSID, dn)
				#sambaSID = None

		new = {}
		new['objectClass'] = [objectClass]
		new['userAccountControl'] = [self.sambaAcctFlags_to_userAccountControl(sambaAcctFlags)]
		new['badPwdCount'] = [self.sambaBadPasswordCount_to_badPwdCount(sambaBadPasswordCount)]
		new['badPasswordTime'] = [self.sambaBadPasswordTime_to_badPasswordTime(sambaBadPasswordTime)]
		new['pwdLastSet'] = [self.sambaPwdLastSet_to_pwdLastSet(sambaPwdLastSet)]
		new['primaryGroupID'] = [self.sambaPrimaryGroupSID_to_primaryGroupID(sambaPrimaryGroupSID, is_computer)]
		objectSid = self.sambaSID_to_objectSid(sambaSID)
		new['objectSid'] = [objectSid]
                if objectClass == 'computer':
                    sAMAccountName = self.name_to_sAMAccountName("%s$" % name)
                else:
		    sAMAccountName = self.name_to_sAMAccountName(name)
		    if self.user_principal_name:
                       userPrincipalName = "%s@%s" % (sAMAccountName, basedn.replace("DC=", "").replace(",", "."))
                       new['userPrincipalName'] = [userPrincipalName]
		new['sAMAccountName'] = [sAMAccountName]
		new['dBCSPwd'] = [self.sambaLMPassword_to_dBCSPwd(sambaLMPassword)]
		unicodePwd = self.sambaNTPassword_to_unicodePwd(sambaNTPassword)
		new['unicodePwd'] = [unicodePwd]
		new['ntPwdHistory'] = [self.sambaPasswordHistory_to_ntPwdHistory(unicodePwd, sambaPasswordHistory)]
		new['scriptPath'] = [self.sambaLogonScript_to_scriptPath(sambaLogonScript)]
		new['profilePath'] = [self.sambaProfilePath_to_profilePath(sambaProfilePath)]
		new['homeDrive'] = [self.sambaHomeDrive_to_homeDrive(sambaHomeDrive)]
		new['homeDirectory'] = [self.sambaHomePath_to_homeDirectory(sambaHomePath)]
		new['logonHours'] = [self.sambaLogonHours_to_logonHours(sambaLogonHours)]
		new['userParameters'] = [self.sambaMungedDial_to_userParameters(sambaMungedDial)]
		new['unixHomeDirectory'] = [self.homeDirectory_to_unixHomeDirectory(homeDirectory)]
		new['unixUserPassword'] = [self.userPassword_to_unixUserPassword(userPassword)]
		#new['codePage'] = [0]
		#new['countryCode'] = [0]
		#new['lastLogoff'] = [0]
		#new['lastLogon'] = [0]
		#new['accountExpires'] = [9223372036854775807]
		#new['logonCount'] = [0]
		#new['pwdCanChange'] = [sambaPwdCanChange: 0]
		#new['kickOffTime'] = [sambaKickoffTime: 0]
		#new['???'] = [sambaLogonTime: 1142404766]

		for attr, val in old.items():
			keep = False
			remove = False
			try:
				remove = self.remove_attrs[attr]
			except KeyError:
				pass

			try:
				keep = self.keep_attrs[attr]
			except KeyError:
				pass

			assert keep != remove, 'keep[%s] remove[%s] error attr[%s] in: %s\n' % (str(keep), str(remove), attr, str(old))

			if not keep:
				continue

			assert attr not in new, \
				'attribute[%s] already exists in new[%s]\n' \
				% (attr, new[attr])

			new[attr] = val

		#print 'old:\n%s\nnew:\n%s' % (str(old), str(new))

                #Add/replace requested attributes
                for key, value in replace_attrs.items():
                    if key in new:
                        for var, val in env_var_mapping.items():
                            if var in value:
                               value = value.replace(var, new[val][0])
                    new[key] = [value]

		entry = {}
		for attr, val in new.items():
			assert attr not in entry, \
				'attribute[%s] already exists in entry[%s]\n' \
				% (attr, entry[attr])

			if len(val) == 0 or val[0] == None:
				continue

                        #Remove new attributes - use with caution
                        if attr not in remove_output_attributes:
                            entry[attr] = val

                #disable the account if no unicodePwd present
                if disable_if_no_unicodePwd == True and 'unicodePwd' not in entry:
                    if 'userAccountControl' in entry:
                        uac = int(entry['userAccountControl'][0]) | UF_ACCOUNTDISABLE
                        entry['userAccountControl'] = ["%d" % uac]

		self.insert_sAMAccountName(sAMAccountName, dn)
		self.insert_objectSid(objectSid, dn, domain=True)
		self.insert_DN(dn, entry)
                #remove ObjectSid entry if needed
                if output_ignore_sids == True:
                    del entry['objectSid']
                if output_display == True:
		    self.insert_Operation('add', dn, entry)

		#print 'old:\n%s\nnew:\n%s\nentry:\n%s' % (old, new, entry)
		return dn, entry, name, objectSid

	def convert_sambaGroupMapping(self, old, baseou, basedn,
                                      remove_input_attributes=[],
                                      remove_output_attributes=[],
                                      output_ignore_sids=False,
                                      output_display=True):

		name = old['cn'][0]
		#try:
		#	ou = old['ou'][0]
		#except:
		#	ou = 'DefaultOU'
		#	pass

		ou_dn = "%s,%s" % (baseou, basedn)
		if self.lookup_DN(ou_dn) is None:
			self.insert_organizationalUnit(ou_dn)

		dn = "CN=%s,%s" % (name, ou_dn)

		try:
			sambaGroupType = old['sambaGroupType'][0]
		except:
			sambaGroupType = None
			pass

		try:
			sambaSID = old['sambaSID'][0]
		except:
			sambaSID = None
			pass

		new = {}
		new['objectClass'] = ['group']
		new['groupType'] = [self.sambaGroupType_to_groupType(sambaGroupType)]
		objectSid = self.sambaSID_to_objectSid(sambaSID)
		new['objectSid'] = [objectSid]
		sAMAccountName = self.name_to_sAMAccountName(name)
		new['sAMAccountName'] = [sAMAccountName]

		for attr, val in old.items():
			keep = False
			remove = False
			try:
				remove = self.remove_attrs[attr]
			except KeyError:
				pass

			try:
				keep = self.keep_attrs[attr]
			except KeyError:
				pass

                        #lets check our remove list and override/set the course of action
                        if attr in remove_input_attributes:
                            remove = True

			assert keep != remove, 'keep[%s] remove[%s] error attr[%s] in: %s\n' % (str(keep), str(remove), attr, str(old))

			if not keep:
				continue

			assert attr not in new, \
				'attribute[%s] already exists in new[%s]\n' \
				% (attr, new[attr])

			new[attr] = val

		#print 'old:\n%s\nnew:\n%s' % (str(old), str(new))

		entry = {}
		for attr, val in new.items():
			assert attr not in entry, \
				'attribute[%s] already exists in entry[%s]\n' \
				% (attr, entry[attr])

			if len(val) == 0 or val[0] == None:
				continue

                        #Remove new attributes - use with caution
                        if attr not in remove_output_attributes:
                            entry[attr] = val

		dom, rid = self.SID_split(objectSid)
		assert dom == self.domain_sid or dom == self.builtin_sid, \
			"sid[%s] doesn't belong to domain[%s]" % (sid, domain_sid)

		if rid < 1000:
			# we don't need to create wellknown SIDs
			dn = "<SID=%s>" % (objectSid)
			entry = {}
			self.insert_sAMAccountName(sAMAccountName, dn)
			self.insert_objectSid(objectSid, dn, wellknown_rid=True)
			self.insert_DN(dn, entry)
			print '# wellknown SID: %s => %s\n' %(objectSid, dn)
			#assert False
		else:
			self.insert_sAMAccountName(sAMAccountName, dn)
			self.insert_objectSid(objectSid, dn)
			self.insert_DN(dn, entry)
                        #remove ObjectSid entry if needed
                        if output_ignore_sids == True:
                            del entry['objectSid']
                        if output_display == True:
                            self.insert_Operation('add', dn, entry)

		#print 'old:\n%s\nnew:\n%s\nentry:\n%s' % (old, new, entry)
		return dn, entry, name, objectSid

	def convert_sambaGroupMapping_Members(self, dn, old):

		try:
			sambaSID = old['sambaSID'][0]
		except:
			sambaSID = None
			pass

		try:
			memberUids = old['memberUid']
		except:
			memberUids = []
			pass

		try:
			sambaSIDList = old['sambaSIDList']
			assert len(memberUids) == 0, "old[%s]:\n%s\n" % ( dn, old )
		except:
			sambaSIDList = []
			pass

		objectSid = self.sambaSID_to_objectSid(sambaSID)
		dom, rid = self.SID_split(objectSid)

		members = []
		for i in range(0, len(memberUids)):
			u = memberUids[i]
			w = self.get_DN_by_sAMAccountName(u)
			e = self.lookup_DN(w)
			try:
				p = e[1]['primaryGroupID'][0]
			except:
				p = '0'
			if (int(p) == int(rid)):
				print '# skip member: %s of DN %s, %s is primary group\n' \
					% (u, dn, p)
				continue
			#print '# add member: %s of DN %s, %s is not primary group(%s)\n' \
			#	% (u, dn, p, rid)
			members.append(w)

		for i in range(0, len(sambaSIDList)):
			s = self.sambaSID_to_objectSid(sambaSIDList[i])
			dom2, rid2 = self.SID_split(s)
			if rid < 1000 and rid2 < 1000:
				print '# skip wellknown member: <SID=%s> of DN %s\n' \
					% (s, dn)
				continue
			w = self.get_DN_by_SID(s)
			e = self.lookup_DN(w)
			try:
				p = e[1]['primaryGroupID'][0]
			except:
				p = '0'
			if (int(p) == int(rid)):
				print '# skip member: %s of DN %s, %s is primary group\n' \
					% (u, dn, p)
				continue
			#print '# add member: %s of DN %s, %s is not primary group(%s)\n' \
			#	% (s, dn, p, rid)
			members.append(w)

		if len(members) == 0:
			return None

		modlist = [(ldap.MOD_ADD, 'member', members)]

		self.insert_Operation('modify', dn, modlist)

		#print '# memberships for DN[%s] (%d/%d) %d:%r\n' % \
		#	(dn, len(memberUids), len(sambaSIDList), len(members), members)

		#print 'old:\n%s\nnew:\n%s\nentry:\n%s' % (old, new, entry)
		return modlist

	def convertObjects(self,
                           remove_input_attributes=[],
                           remove_output_attributes=[],
                           output_ignore_sids=False,
                           import_type=IMPORT_TYPE_ALL,
                           user_replace_attrs={},
                           computer_replace_attrs={}):
		#- import domain policies
		#- import trusted domains
		#- import builtin groups
		#- import local domain groups
		#- import domain groups
		#- import group nestings
		#- import users
		#- import users group memberships

		self.domain.results = self.ldap.search(
					self.domain.basedn,
					self.domain.scope,
					self.domain.filterstr)
		assert len(self.domain.results) == 1
		#for i, r in enumerate(self.domain.results):
		#	print 'domain:%d: %s\n' % (i, str(r))
		for k, v in self.domain.results.items():
			#print '%s: %s\n' % (str(k), str(v))
			self.domain_sid = self.sambaSID_to_objectSid(v['sambaSID'][0])

		old_group_DNs = []
		old_group_entries = {}
		#
		# a few posixGroups are not sambaGroupMappings
		# and there're builtin groups
		#
                if (import_type & IMPORT_TYPE_GROUPS) == IMPORT_TYPE_GROUPS:
                    self.groups.results = self.ldap.search(
                                            self.groups.basedn,
                                            self.groups.scope,
                                            self.groups.filterstr)
                    #for i, r in enumerate(self.groups.results):
                    #	print 'groups:%d: %s\n' % (i, str(r))
                    for k, v in self.groups.results.items():
                    #	print '%s: %s\n' % (str(k), str(v))
                        dn, entry, name, sid = self.convert_sambaGroupMapping(v,
                                            self.groups.importou,
                                            self.groups.importdn,
                                            remove_input_attributes=remove_input_attributes,
                                            remove_output_attributes=remove_output_attributes,
                                            output_ignore_sids=output_ignore_sids,
                                            output_display=bool(import_type & IMPORT_TYPE_GROUPS))
                        old_group_DNs.append(dn)
                        old_group_entries[dn] = v

                if (import_type & IMPORT_TYPE_TRUSTS) == IMPORT_TYPE_TRUSTS:
		    self.trusts.results = self.ldap.search(
                                            self.trusts.basedn,
                                            self.trusts.scope,
                                            self.trusts.filterstr)
		    #for i, r in enumerate(self.trusts.results):
		    #	print 'trusts:%d: %s\n' % (i, str(r))
		    for k, v in self.trusts.results.items():
		    #	print '%s: %s\n' % (str(k), str(v))
			    dn, entry, name, sid = self.convert_sambaSamAccount(v,
                                    self.trusts.importou,
				    self.trusts.importdn,
				    objectClass="TODO-TRUSTS",
				    keep_sid=False,
                                    remove_input_attributes=remove_input_attributes,
                                    remove_output_attributes=remove_output_attributes,
                                    output_display=bool(import_type & IMPORT_TYPE_TRUSTS))

                if (import_type & IMPORT_TYPE_COMPUTERS) == IMPORT_TYPE_COMPUTERS:
                    self.computers.results = self.ldap.search(
                                             self.computers.basedn,
                                             self.computers.scope,
                                             self.computers.filterstr)
                    #for i, r in enumerate(self.computers.results):
                    #	print 'computers:%d: %s\n' % (i, str(r))
                    for k, v in self.computers.results.items():
		    #	print '%s: %s\n' % (str(k), str(v))
                        dn, entry, name, sid = self.convert_sambaSamAccount(v,
                                    self.computers.importou,
                                    self.computers.importdn,
                                    objectClass="computer",
                                    keep_sid=False, is_computer=True,
                                    remove_input_attributes=remove_input_attributes,
                                    remove_output_attributes=remove_output_attributes,
                                    output_ignore_sids=output_ignore_sids,
                                    output_display=bool(import_type & IMPORT_TYPE_COMPUTERS),
                                    replace_attrs=computer_replace_attrs)

                if (import_type & IMPORT_TYPE_USERS) == IMPORT_TYPE_USERS or (import_type & IMPORT_TYPE_GROUPS) == IMPORT_TYPE_GROUPS:
                    self.users.results = self.ldap.search(
                                            self.users.basedn,
                                            self.users.scope,
                                            self.users.filterstr)
                    #for i, r in enumerate(self.users.results):
                    #	print 'users:%d: %s\n' % (i, str(r))
                    for k, v in self.users.results.items():
                    #	print '%s: %s\n' % (str(k), str(v))
                        dn, entry, name, sid = self.convert_sambaSamAccount(v,
                                        self.users.importou,
                                        self.users.importdn,
                                        remove_input_attributes=remove_input_attributes,
                                        remove_output_attributes=remove_output_attributes,
                                        output_ignore_sids=output_ignore_sids,
                                        output_display=bool(import_type & IMPORT_TYPE_USERS),
                                        replace_attrs=user_replace_attrs,
                                        disable_if_no_unicodePwd=True)

		#
		# Groups Memberships
		#
                if (import_type & IMPORT_TYPE_GROUPS) == IMPORT_TYPE_GROUPS:
		    for dn in old_group_DNs:
			v = old_group_entries[dn]
			modlist = self.convert_sambaGroupMapping_Members(dn, v)

	def getModifyLDIF(self, output):
		ldifout = MyLDIFWriter(output)

		for i, op in enumerate(self.new_operations):
			output.write("# op[%s]: %u\n" % (op[0], i))

			ldifout.unparse(op[1], op[2])

	def getSummary(self):
		ret = "domain[%s] rid[%d-%d] objects[%d]" % (\
			self.domain_sid,
			self.new_lowest_rid,
			self.new_highest_rid,
			len(self.new_DNs))

		return ret

class MyLDAPRemote:
    """Base clas for remote ldap operations"""

    def __init__(self, uri, binddn="", bindpwd="", starttls=False):
        try:
            self.ldap = ldap.initialize(uri)
            if starttls == True:
                self.ldap.start_tls_s()

            self.ldap.simple_bind_s(binddn, bindpwd)
        except ldap.LDAPError, e:
            print e
            sys.exit()

    def search(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0):
        results = {}
        try:
            search_results = self.ldap.search_s(base, scope, filterstr, attrlist, attrsonly)
        except ldap.LDAPError, e:
            print e
            sys.exit()

        for search_result in search_results:
            (dn, attributes) = search_result
            results[dn] = attributes

        return results

class cmd_import_from_ldap:
    """Import accounts from ldap/ldif"""

    parser = OptionParser()

    def __init__(self):
        self.parser.add_option("--input_ldif", dest="input_ldif", help="ldif file containing data to import")
        self.parser.add_option("-b", "--input_basedn", dest="input_basedn", help="input data baseDN")
        self.parser.add_option("--input_domain_name", dest="input_domain_name", help="input domain name")
        self.parser.add_option("--output_basedn", dest="output_basedn", help="output data baseDN")
        self.parser.add_option("--output_ignore_sids", dest="output_ignore_sids", action="store_true", default=False, help="do not output objectSid's")
        self.parser.add_option("--remove_input_attributes", dest="remove_input_attributes", default=None, metavar="attrname1,attrname2,atrname3",
                               help="comma separated list of input attribute names that should be ignored during the conversion")
        self.parser.add_option("--remove_output_attributes", dest="remove_output_attributes", default=None, metavar="attrname1,attrname2,atrname3",
                               help="comma separated list of output attribute names that should be removed after the conversion. Use with great caution")
        self.parser.add_option("--import_accounts", dest="import_accounts", default="All", metavar="All,Users,Computers,Trusts,Groups",
                               help="comma separated list of account types to import [default: %default]")

        group_users = OptionGroup(self.parser, "Users related options", "Options used for operations performed on user objects")
        group_users.add_option("--output_users_ou", dest="output_users_ou", default="OU=Imported Users",
                               help="users OU of the output data, without base DN counterpart ([default: %default])")
        group_users.add_option("--user_search_filter", dest="user_search_filter", help="search filter for users")
        group_users.add_option("--input_users_ou", dest="input_users_ou", default=None, help="users OU of the input data, without base DN counterpart")
        group_users.add_option("--user_replace_attributes", dest="user_replace_attributes", default=None, metavar="homeDrive;H:;profilePath;\\\\my.server\\profiles",
                               help="semicolon separated list of attributes and values to replace or add for all processed user accounts")
        group_users.add_option('--create_user_principal_name',
                   help=("autocreate the userPrincipalName attribute for imported users (default=no)"),
                   action="store_true", dest='user_principal_name', default=False)
        self.parser.add_option_group(group_users)

        group_computers = OptionGroup(self.parser, "Computer related options", "Options used for operations performed on computer objects")
        group_computers.add_option("--output_computers_ou", dest="output_computers_ou", default="OU=Imported Computers",
                                   help="computers OU of the output data, without baseDN counterpart [default: %default])")
        group_computers.add_option("--computer_search_filter", dest="computer_search_filter", help="search filter for computers")
        group_computers.add_option("--input_computers_ou", dest="input_computers_ou", default=None, help="computers OU of the input data, without base DN counterpart")
        group_computers.add_option("--computer_replace_attributes", dest="computer_replace_attributes", default=None, metavar="description;Accounting computer",
                               help="semicolon separated list of attributes and values to replace or add for all processed computer accounts")
        self.parser.add_option_group(group_computers)

        group_trusts = OptionGroup(self.parser, "Trusts related options", "Options used for operations performed on trust objects")
        group_trusts.add_option("--output_trusts_ou", dest="output_trusts_ou", default="OU=Imported Trusts",
                                help="trusts OU of the output data, without base DN counterpart [default: %default])")
        group_trusts.add_option("--input_trusts_ou", dest="input_trusts_ou", default=None, help="trusts OU of the input data, without base DN counterpart")
        self.parser.add_option_group(group_trusts)

        group_groups = OptionGroup(self.parser, "Group related options", "Options used for operations performed on group objects")
        group_groups.add_option("--output_groups_ou", dest="output_groups_ou", default="OU=Imported Groups",
                                help="groups OU of the output data, without baseDN counterpart [default: %default])")
        group_groups.add_option("--group_search_filter", dest="group_search_filter", help="search filter for groups")
        group_groups.add_option("--input_groups_ou", dest="input_groups_ou", default=None, help="groups OU of the input data, without base DN counterpart")
        self.parser.add_option_group(group_groups)

        group_openldap = OptionGroup(self.parser, "LDAP Connection Related Options", "Options used for LDAP backend operations")
        group_openldap.add_option("-Z", "--ldap_starttls", dest="ldap_starttls", action="store_true", default=False, help="Start TLS")
        group_openldap.add_option("-D", "--ldap_binddn", dest="ldap_binddn", default="", help="bind DN")
        group_openldap.add_option("-H", "--ldap_uri", dest="ldap_uri", help="LDAP Uniform Resource Identifier")
        group_openldap.add_option("-w", "--ldap_bindpwd", dest="ldap_bindpw", metavar="password", default="", help="bind password (for simple authentication)")
        group_openldap.add_option("-W", "--ldap_prompt_bindpwd", dest="ldap_prompt_bindpwd", action="store_true", default=False, help="prompt for bind password")
        self.parser.add_option_group(group_openldap)

    def run(self):
        (options, args) = self.parser.parse_args()
        remove_input_attrs = []
        remove_output_attrs = []

        if options.remove_input_attributes is not None:
            remove_input_attrs = options.remove_input_attributes.split(',')

        if options.remove_output_attributes is not None:
            remove_output_attrs = options.remove_output_attributes.split(',')

        if options.input_ldif is not None:
            myldap = MyLDAP(open(options.input_ldif, 'rb'))
        else:
            if options.ldap_prompt_bindpwd == True:
                binddn_passwd = getpass("Enter LDAP Password:")
            else:
                binddn_passwd = options.ldap_bindpw
            myldap = MyLDAPRemote(options.ldap_uri, options.ldap_binddn, binddn_passwd, options.ldap_starttls)

        #Define the import types
        import_type = 0
        for t in options.import_accounts.split(','):
            import_type |= import_types[t]

        #Processing specific domain
        domain_filter = '(&(sambaDomainName=%s)(objectClass=sambaDomain))' % options.input_domain_name
        domain_search = ImportSamba3SearchState(options.input_basedn, ldap.SCOPE_SUBTREE, domain_filter, None, options.output_basedn)

        #Processing domain trusts
        trust_filter = '(&(objectClass=sambaSamAccount)(TODO=TODO))'
        trusts_input_dn = options.input_basedn
        if options.input_trusts_ou is not None:
            trusts_input_dn = '%s,%s' % (options.input_trusts_ou, options.input_basedn)

        trusts_search = ImportSamba3SearchState(trusts_input_dn, ldap.SCOPE_SUBTREE, trust_filter, options.output_trusts_ou, options.output_basedn)

        #Processing computer accounts
        computer_filter = 'objectClass=sambaSamAccount'
        if options.computer_search_filter is not None:
            computer_filter = '(&(%s)(%s))' % (computer_filter, options.computer_search_filter)

        computers_input_dn = options.input_basedn
        if options.input_computers_ou is not None:
            computers_input_dn = '%s,%s' % (options.input_computers_ou, options.input_basedn)

        computer_replace_attrs = {}
        if options.computer_replace_attributes is not None:
           replaces = options.computer_replace_attributes.split(';')
           assert len(replaces) % 2 == 0, "computer_replace_attributes must contain one or more attribute-value pairs"
           for j in range(0, len(replaces), 2):
               if len(replaces[j]) > 0 and len(replaces[j+1]) > 0:
                   computer_replace_attrs[replaces[j]] = replaces[j+1]

        computers_search = ImportSamba3SearchState(computers_input_dn, ldap.SCOPE_SUBTREE, computer_filter, options.output_computers_ou, options.output_basedn)

        #Processing user accounts
        user_replace_attrs = {}
        if options.user_replace_attributes is not None:
           replaces = options.user_replace_attributes.split(';')
	   assert len(replaces) % 2 == 0, "user_replace_attributes must contain one or more attribute-value pairs"
           for j in range(0, len(replaces), 2):
               if len(replaces[j]) > 0 and len(replaces[j+1]) > 0:
                   user_replace_attrs[replaces[j]] = replaces[j+1]

        user_filter = 'objectClass=sambaSamAccount'
        if options.user_search_filter is not None:
            user_filter = '(&(%s)(%s))' % (user_filter, options.user_search_filter)

        users_input_dn = options.input_basedn
        if options.input_users_ou is not None:
            users_input_dn = '%s,%s' % (options.input_users_ou, options.input_basedn)

        users_search = ImportSamba3SearchState(users_input_dn, ldap.SCOPE_SUBTREE, user_filter, options.output_users_ou, options.output_basedn)

        #Processing groups
        group_filter = '(&(objectClass=posixGroup)(objectClass=sambaGroupMapping))'
        if options.group_search_filter is not None:
            group_filter = '(&%s(%s))' % (group_filter, options.group_search_filter)

        groups_input_dn = options.input_basedn
        if options.input_groups_ou is not None:
            groups_input_dn = '%s,%s' % (options.input_groups_ou, options.input_basedn)

        groups_search = ImportSamba3SearchState(groups_input_dn, ldap.SCOPE_SUBTREE, group_filter, options.output_groups_ou, options.output_basedn)

        s3s4 = ImportSamba3LDIF(
	       myldap,
	       domain_search,
	       trusts_search,
	       computers_search,
	       users_search,
	       groups_search,
               output_ignore_sids=options.output_ignore_sids,
               remove_input_attributes=remove_input_attrs,
               remove_output_attributes=remove_output_attrs,
               import_type=import_type,
               user_replace_attrs=user_replace_attrs,
               computer_replace_attrs=computer_replace_attrs,
	       user_principal_name=options.user_principal_name)

        print "# %s\n" % (s3s4.getSummary())
        s3s4.getModifyLDIF(sys.stdout)

if __name__ == "__main__":
    ldap_cmd = cmd_import_from_ldap()
    ldap_cmd.run()
