576f4b85f1a2126857349d979091a52cee968de6
[metze/samba/wip.git] / source4 / scripting / devel / repl_cleartext_pwd.py
1 #!/usr/bin/env python
2 #
3 # Copyright Stefan Metzmacher 2011-2012
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #
18 # This is useful to sync passwords from an AD domain.
19 #
20 #  $
21 #  $ source4/scripting/devel/repl_cleartext_pwd.py \
22 #       -Uadministrator%A1b2C3d4 \
23 #       172.31.9.219 DC=bla,DC=base /tmp/cookie cleartext_utf8 131085 displayName
24 #  # starting at usn[0]
25 #  dn: CN=Test User1,CN=Users,DC=bla,DC=base
26 #  cleartext_utf8: A1b2C3d4
27 #  displayName:: VABlAHMAdAAgAFUAcwBlAHIAMQA=
28 #
29 #  # up to usn[16449]
30 #  $
31 #  $ source4/scripting/devel/repl_cleartext_pwd.py \
32 #       -Uadministrator%A1b2C3d4
33 #       172.31.9.219 DC=bla,DC=base cookie_file cleartext_utf8 131085 displayName
34 #  # starting at usn[16449]
35 #  # up to usn[16449]
36 #  $
37 #
38
39 import sys
40
41 # Find right direction when running from source tree
42 sys.path.insert(0, "bin/python")
43
44 import samba.getopt as options
45 from optparse import OptionParser
46
47 from samba.dcerpc import drsuapi, drsblobs, misc
48 from samba.ndr import ndr_pack, ndr_unpack, ndr_print
49
50 import binascii
51 import hashlib
52 import Crypto.Cipher.ARC4
53 import struct
54 import os
55
56 from ldif import LDIFWriter
57
58 class globals:
59     def __init__(self):
60         self.global_objs = {}
61         self.ldif = LDIFWriter(sys.stdout)
62
63     def add_attr(self, dn, attname, vals):
64         if dn not in self.global_objs:
65            self.global_objs[dn] = {}
66         self.global_objs[dn][attname] = vals
67
68     def print_all(self):
69         for dn, obj in self.global_objs.items():
70            self.ldif.unparse(dn, obj)
71            continue
72         self.global_objs = {}
73
74 def attid_equal(a1,a2):
75     return (a1 & 0xffffffff) == (a2 & 0xffffffff)
76
77 ########### main code ###########
78 if __name__ == "__main__":
79     parser = OptionParser("repl_cleartext_pwd.py [options] server dn cookie_file cleartext_name [attid attname]")
80     sambaopts = options.SambaOptions(parser)
81     credopts = options.CredentialsOptions(parser)
82     parser.add_option_group(credopts)
83
84     (opts, args) = parser.parse_args()
85
86     if len(args) < 4 or len(args) == 5:
87         parser.error("more arguments required")
88
89     server = args[0]
90     dn = args[1]
91     cookie_file = args[2]
92     if len(cookie_file) == 0:
93         cookie_file = None
94     cleartext_name = args[3]
95     if len(args) >= 5:
96         try:
97             attid = int(args[4], 16)
98         except:
99             attid = int(args[4])
100         attname = args[5]
101     else:
102         attid = -1
103         attname = None
104
105     lp = sambaopts.get_loadparm()
106     creds = credopts.get_credentials(lp)
107
108     if not creds.authentication_requested():
109         parser.error("You must supply credentials")
110
111     gls = globals()
112     try:
113        f = open(cookie_file, 'r')
114        store_blob = f.read()
115        f.close()
116
117        store_hdr = store_blob[0:28]
118        (store_version, \
119         store_dn_len, store_dn_ofs, \
120         store_hwm_len, store_hwm_ofs, \
121         store_utdv_len, store_utdv_ofs) = \
122         struct.unpack("<LLLLLLL", store_hdr)
123
124        store_dn = store_blob[store_dn_ofs:store_dn_ofs+store_dn_len]
125        store_hwm_blob = store_blob[store_hwm_ofs:store_hwm_ofs+store_hwm_len]
126        store_utdv_blob = store_blob[store_utdv_ofs:store_utdv_ofs+store_utdv_len]
127
128        store_hwm = ndr_unpack(drsuapi.DsReplicaHighWaterMark, store_hwm_blob)
129        store_utdv = ndr_unpack(drsblobs.replUpToDateVectorBlob, store_utdv_blob)
130
131        assert store_dn == dn
132        #print "%s" % ndr_print(store_hwm)
133        #print "%s" % ndr_print(store_utdv)
134     except:
135        store_dn = dn
136        store_hwm = drsuapi.DsReplicaHighWaterMark()
137        store_hwm.tmp_highest_usn  = 0
138        store_hwm.reserved_usn     = 0
139        store_hwm.highest_usn      = 0
140        store_utdv = None
141
142     binding_str = "ncacn_ip_tcp:%s[spnego,seal]" % server
143
144     drs_conn = drsuapi.drsuapi(binding_str, lp, creds)
145
146     bind_info = drsuapi.DsBindInfoCtr()
147     bind_info.length = 28
148     bind_info.info = drsuapi.DsBindInfo28()
149     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_BASE
150     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ASYNC_REPLICATION
151     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_REMOVEAPI
152     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_MOVEREQ_V2
153     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHG_COMPRESS
154     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V1
155     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_RESTORE_USN_OPTIMIZATION
156     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_KCC_EXECUTE
157     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ADDENTRY_V2
158     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_LINKED_VALUE_REPLICATION
159     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V2
160     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_INSTANCE_TYPE_NOT_REQ_ON_MOD
161     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_CRYPTO_BIND
162     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GET_REPL_INFO
163     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_STRONG_ENCRYPTION
164     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V01
165     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_TRANSITIVE_MEMBERSHIP
166     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ADD_SID_HISTORY
167     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_POST_BETA3
168     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GET_MEMBERSHIPS2
169     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V6
170     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_NONDOMAIN_NCS
171     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8
172     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V5
173     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V6
174     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ADDENTRYREPLY_V3
175     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V7
176     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_VERIFY_OBJECT
177     (info, drs_handle) = drs_conn.DsBind(misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID), bind_info)
178
179     null_guid = misc.GUID()
180
181     naming_context = drsuapi.DsReplicaObjectIdentifier()
182     naming_context.dn              = dn
183     highwatermark                  = store_hwm
184     uptodateness_vector            = None
185     if store_utdv is not None:
186         uptodateness_vector = drsuapi.DsReplicaCursorCtrEx()
187         if store_utdv.version == 1:
188             uptodateness_vector.cursors = store_utdv.cursors
189         elif store_utdv.version == 2:
190             cursors = []
191             for i in range(0, store_utdv.ctr.count):
192                 cursor = drsuapi.DsReplicaCursor()
193                 cursor.source_dsa_invocation_id = store_utdv.ctr.cursors[i].source_dsa_invocation_id
194                 cursor.highest_usn = store_utdv.ctr.cursors[i].highest_usn
195                 cursors.append(cursor)
196             uptodateness_vector.cursors = cursors
197
198     req8 = drsuapi.DsGetNCChangesRequest8()
199
200     req8.destination_dsa_guid           = null_guid
201     req8.source_dsa_invocation_id       = null_guid
202     req8.naming_context                 = naming_context
203     req8.highwatermark                  = highwatermark
204     req8.uptodateness_vector            = uptodateness_vector
205     req8.replica_flags                  = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
206                                            drsuapi.DRSUAPI_DRS_PER_SYNC |
207                                            drsuapi.DRSUAPI_DRS_GET_ANC |
208                                            drsuapi.DRSUAPI_DRS_NEVER_SYNCED |
209                                            drsuapi.DRSUAPI_DRS_WRIT_REP)
210     req8.max_object_count = 402
211     req8.max_ndr_size = 402116
212     req8.extended_op = 0
213     req8.fsmo_info = 0
214     req8.partial_attribute_set = None
215     req8.partial_attribute_set_ex = None
216     req8.mapping_ctr.num_mappings = 0
217     req8.mapping_ctr.mappings = None
218
219     user_session_key = drs_conn.user_session_key
220
221     print "# starting at usn[%d]" % (highwatermark.highest_usn)
222
223     while True:
224         (level, ctr) = drs_conn.DsGetNCChanges(drs_handle, 8, req8)
225         if ctr.first_object == None and ctr.object_count != 0:
226             raise RuntimeError("DsGetNCChanges: NULL first_object with object_count=%u" % (ctr.object_count))
227
228         obj_item = ctr.first_object
229         while obj_item is not None:
230             obj = obj_item.object
231
232             if obj.identifier is None:
233                 obj_item = obj_item.next_object
234                 continue
235
236             #print '%s' % obj.identifier.dn
237
238             is_deleted = False
239             for i in range(0, obj.attribute_ctr.num_attributes):
240                 attr = obj.attribute_ctr.attributes[i]
241                 if attid_equal(attr.attid, drsuapi.DRSUAPI_ATTID_isDeleted):
242                     is_deleted = True
243             if is_deleted:
244                 obj_item = obj_item.next_object
245                 continue
246
247             spl_crypt = None
248             attvals = None
249             for i in range(0, obj.attribute_ctr.num_attributes):
250                 attr = obj.attribute_ctr.attributes[i]
251                 if attid_equal(attr.attid, attid):
252                     attvals = []
253                     for j in range(0, attr.value_ctr.num_values):
254                         assert attr.value_ctr.values[j].blob is not None
255                         attvals.append(attr.value_ctr.values[j].blob)
256                 if not attid_equal(attr.attid, drsuapi.DRSUAPI_ATTID_supplementalCredentials):
257                     continue
258                 assert attr.value_ctr.num_values <= 1
259                 if attr.value_ctr.num_values == 0:
260                     break
261                 assert attr.value_ctr.values[0].blob is not None
262                 spl_crypt = attr.value_ctr.values[0].blob
263
264             if spl_crypt is None:
265                 obj_item = obj_item.next_object
266                 continue
267
268             assert len(spl_crypt) >= 20
269             confounder = spl_crypt[0:16]
270             enc_buffer = spl_crypt[16:]
271
272             m5 = hashlib.md5()
273             m5.update(user_session_key)
274             m5.update(confounder)
275             enc_key = m5.digest()
276
277             rc4 = Crypto.Cipher.ARC4.new(enc_key)
278             plain_buffer = rc4.decrypt(enc_buffer)
279
280             (crc32_v) = struct.unpack("<L", plain_buffer[0:4])
281             attr_val = plain_buffer[4:]
282             crc32_c = binascii.crc32(attr_val) & 0xffffffff
283             assert int(crc32_v[0]) == int(crc32_c), "CRC32 0x%08X != 0x%08X" % (crc32_v[0], crc32_c)
284
285             spl = ndr_unpack(drsblobs.supplementalCredentialsBlob, attr_val)
286
287             #print '%s' % ndr_print(spl)
288
289             cleartext_hex = None
290
291             for i in range(0, spl.sub.num_packages):
292                 pkg = spl.sub.packages[i]
293                 if pkg.name != "Primary:CLEARTEXT":
294                     continue
295                 cleartext_hex = pkg.data
296
297             if cleartext_hex is not None:
298                 cleartext_utf16 = binascii.a2b_hex(cleartext_hex)
299                 cleartext_unicode = unicode(cleartext_utf16, 'utf-16-le')
300                 cleartext_utf8 = cleartext_unicode.encode('utf-8')
301
302                 gls.add_attr(obj.identifier.dn, cleartext_name, [cleartext_utf8])
303
304                 if attvals is not None:
305                     gls.add_attr(obj.identifier.dn, attname, attvals)
306
307             krb5_old_hex = None
308
309             for i in range(0, spl.sub.num_packages):
310                 pkg = spl.sub.packages[i]
311                 if pkg.name != "Primary:Kerberos":
312                     continue
313                 krb5_old_hex = pkg.data
314
315             if krb5_old_hex is not None:
316                 krb5_old_raw = binascii.a2b_hex(krb5_old_hex)
317                 krb5_old = ndr_unpack(drsblobs.package_PrimaryKerberosBlob, krb5_old_raw, allow_remaining=True)
318
319                 #print '%s' % ndr_print(krb5_old)
320
321             krb5_new_hex = None
322
323             for i in range(0, spl.sub.num_packages):
324                 pkg = spl.sub.packages[i]
325                 if pkg.name != "Primary:Kerberos-Newer-Keys":
326                     continue
327                 krb5_new_hex = pkg.data
328
329             if krb5_new_hex is not None:
330                 krb5_new_raw = binascii.a2b_hex(krb5_new_hex)
331                 krb5_new = ndr_unpack(drsblobs.package_PrimaryKerberosBlob, krb5_new_raw, allow_remaining=True)
332
333                 #print '%s' % ndr_print(krb5_new)
334
335             obj_item = obj_item.next_object
336
337         gls.print_all()
338
339         if ctr.more_data == 0:
340             store_hwm = ctr.new_highwatermark
341
342             store_utdv = drsblobs.replUpToDateVectorBlob()
343             store_utdv.version = ctr.uptodateness_vector.version
344             store_utdv_ctr = store_utdv.ctr
345             store_utdv_ctr.count = ctr.uptodateness_vector.count
346             store_utdv_ctr.cursors = ctr.uptodateness_vector.cursors
347             store_utdv.ctr = store_utdv_ctr
348
349             #print "%s" % ndr_print(store_hwm)
350             #print "%s" % ndr_print(store_utdv)
351
352             store_hwm_blob = ndr_pack(store_hwm)
353             store_utdv_blob = ndr_pack(store_utdv)
354
355             #
356             # uint32_t version '1'
357             # uint32_t dn_str_len
358             # uint32_t dn_str_ofs
359             # uint32_t hwm_blob_len
360             # uint32_t hwm_blob_ofs
361             # uint32_t utdv_blob_len
362             # uint32_t utdv_blob_ofs
363             store_hdr_len = 7 * 4
364             dn_ofs = store_hdr_len
365             hwm_ofs = dn_ofs + len(dn)
366             utdv_ofs = hwm_ofs + len(store_hwm_blob)
367             store_blob = struct.pack("<LLLLLLL", 1, \
368                                      len(dn), dn_ofs,
369                                      len(store_hwm_blob), hwm_ofs, \
370                                      len(store_utdv_blob), utdv_ofs) + \
371                                      dn + store_hwm_blob + store_utdv_blob
372
373             tmp_file = "%s.tmp" % cookie_file
374             f = open(tmp_file, 'wb')
375             f.write(store_blob)
376             f.close()
377             os.rename(tmp_file, cookie_file)
378
379             print "# up to usn[%d]" % (ctr.new_highwatermark.highest_usn)
380             break
381         print "# up to tmp_usn[%d]" % (ctr.new_highwatermark.highest_usn)
382         req8.highwatermark.tmp_highest_usn = ctr.new_highwatermark.tmp_highest_usn