2 Unix SMB/CIFS implementation.
4 Extract the user/system database from a remote SamSync server
6 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 #include "libnet/libnet.h"
26 #include "libcli/auth/libcli_auth.h"
27 #include "auth/gensec/gensec.h"
28 #include "auth/credentials/credentials.h"
29 #include "auth/gensec/schannel_proto.h"
30 #include "librpc/gen_ndr/ndr_netlogon.h"
31 #include "librpc/gen_ndr/ndr_netlogon_c.h"
35 * Decrypt and extract the user's passwords.
37 * The writes decrypted (no longer 'RID encrypted' or arcfour encrypted) passwords back into the structure
39 static NTSTATUS
fix_user(TALLOC_CTX
*mem_ctx
,
40 struct creds_CredentialState
*creds
,
42 enum netr_SamDatabaseID database
,
43 struct netr_DELTA_ENUM
*delta
,
47 uint32_t rid
= delta
->delta_id_union
.rid
;
48 struct netr_DELTA_USER
*user
= delta
->delta_union
.user
;
49 struct samr_Password lm_hash
;
50 struct samr_Password nt_hash
;
51 const char *username
= user
->account_name
.string
;
55 if (user
->lm_password_present
) {
56 sam_rid_crypt(rid
, user
->lmpassword
.hash
, lm_hash
.hash
, 0);
57 user
->lmpassword
= lm_hash
;
60 if (user
->nt_password_present
) {
61 sam_rid_crypt(rid
, user
->ntpassword
.hash
, nt_hash
.hash
, 0);
62 user
->ntpassword
= nt_hash
;
66 if (user
->user_private_info
.SensitiveData
) {
68 struct netr_USER_KEYS keys
;
69 data
.data
= user
->user_private_info
.SensitiveData
;
70 data
.length
= user
->user_private_info
.DataLength
;
71 creds_arcfour_crypt(creds
, data
.data
, data
.length
);
72 user
->user_private_info
.SensitiveData
= data
.data
;
73 user
->user_private_info
.DataLength
= data
.length
;
75 nt_status
= ndr_pull_struct_blob(&data
, mem_ctx
, &keys
, (ndr_pull_flags_fn_t
)ndr_pull_netr_USER_KEYS
);
76 if (NT_STATUS_IS_OK(nt_status
)) {
77 if (keys
.keys
.keys2
.lmpassword
.length
== 16) {
79 sam_rid_crypt(rid
, keys
.keys
.keys2
.lmpassword
.pwd
.hash
, lm_hash
.hash
, 0);
80 user
->lmpassword
= lm_hash
;
82 user
->lmpassword
= keys
.keys
.keys2
.lmpassword
.pwd
;
84 user
->lm_password_present
= True
;
86 if (keys
.keys
.keys2
.ntpassword
.length
== 16) {
88 sam_rid_crypt(rid
, keys
.keys
.keys2
.ntpassword
.pwd
.hash
, nt_hash
.hash
, 0);
89 user
->ntpassword
= nt_hash
;
91 user
->ntpassword
= keys
.keys
.keys2
.ntpassword
.pwd
;
93 user
->nt_password_present
= True
;
95 /* TODO: rid decrypt history fields */
97 *error_string
= talloc_asprintf(mem_ctx
, "Failed to parse Sensitive Data for %s:", username
);
98 dump_data(10, data
.data
, data
.length
);
106 * Decrypt and extract the secrets
108 * The writes decrypted secrets back into the structure
110 static NTSTATUS
fix_secret(TALLOC_CTX
*mem_ctx
,
111 struct creds_CredentialState
*creds
,
112 enum netr_SamDatabaseID database
,
113 struct netr_DELTA_ENUM
*delta
,
116 struct netr_DELTA_SECRET
*secret
= delta
->delta_union
.secret
;
117 creds_arcfour_crypt(creds
, secret
->current_cipher
.cipher_data
,
118 secret
->current_cipher
.maxlen
);
120 creds_arcfour_crypt(creds
, secret
->old_cipher
.cipher_data
,
121 secret
->old_cipher
.maxlen
);
127 * Fix up the delta, dealing with encryption issues so that the final
128 * callback need only do the printing or application logic
131 static NTSTATUS
fix_delta(TALLOC_CTX
*mem_ctx
,
132 struct creds_CredentialState
*creds
,
134 enum netr_SamDatabaseID database
,
135 struct netr_DELTA_ENUM
*delta
,
138 NTSTATUS nt_status
= NT_STATUS_OK
;
139 *error_string
= NULL
;
140 switch (delta
->delta_type
) {
141 case NETR_DELTA_USER
:
143 nt_status
= fix_user(mem_ctx
,
151 case NETR_DELTA_SECRET
:
153 nt_status
= fix_secret(mem_ctx
,
166 NTSTATUS
libnet_SamSync_netlogon(struct libnet_context
*ctx
, TALLOC_CTX
*mem_ctx
, struct libnet_SamSync
*r
)
168 NTSTATUS nt_status
, dbsync_nt_status
;
169 TALLOC_CTX
*samsync_ctx
, *loop_ctx
, *delta_ctx
;
170 struct creds_CredentialState
*creds
;
171 struct netr_DatabaseSync dbsync
;
172 struct cli_credentials
*machine_account
;
173 struct dcerpc_pipe
*p
;
174 struct libnet_context
*machine_net_ctx
;
175 struct libnet_RpcConnect
*c
;
176 struct libnet_SamSync_state
*state
;
177 const enum netr_SamDatabaseID database_ids
[] = {SAM_DATABASE_DOMAIN
, SAM_DATABASE_BUILTIN
, SAM_DATABASE_PRIVS
};
180 samsync_ctx
= talloc_named(mem_ctx
, 0, "SamSync top context");
182 if (!r
->in
.machine_account
) {
183 machine_account
= cli_credentials_init(samsync_ctx
);
184 if (!machine_account
) {
185 talloc_free(samsync_ctx
);
186 return NT_STATUS_NO_MEMORY
;
188 cli_credentials_set_conf(machine_account
);
189 nt_status
= cli_credentials_set_machine_account(machine_account
);
190 if (!NT_STATUS_IS_OK(nt_status
)) {
191 r
->out
.error_string
= talloc_strdup(mem_ctx
, "Could not obtain machine account password - are we joined to the domain?");
192 talloc_free(samsync_ctx
);
196 machine_account
= r
->in
.machine_account
;
199 /* We cannot do this unless we are a BDC. Check, before we get odd errors later */
200 if (cli_credentials_get_secure_channel_type(machine_account
) != SEC_CHAN_BDC
) {
202 = talloc_asprintf(mem_ctx
,
203 "Our join to domain %s is not as a BDC (%d), please rejoin as a BDC",
204 cli_credentials_get_domain(machine_account
),
205 cli_credentials_get_secure_channel_type(machine_account
));
206 talloc_free(samsync_ctx
);
207 return NT_STATUS_CANT_ACCESS_DOMAIN_INFO
;
210 c
= talloc(samsync_ctx
, struct libnet_RpcConnect
);
212 r
->out
.error_string
= NULL
;
213 talloc_free(samsync_ctx
);
214 return NT_STATUS_NO_MEMORY
;
217 c
->level
= LIBNET_RPC_CONNECT_DC_INFO
;
218 if (r
->in
.binding_string
) {
219 c
->in
.binding
= r
->in
.binding_string
;
222 c
->in
.binding
= NULL
;
223 c
->in
.name
= cli_credentials_get_domain(machine_account
);
226 /* prepare connect to the NETLOGON pipe of PDC */
227 c
->in
.dcerpc_iface
= &dcerpc_table_netlogon
;
229 /* We must do this as the machine, not as any command-line
230 * user. So we override the credentials in the
232 machine_net_ctx
= talloc(samsync_ctx
, struct libnet_context
);
233 if (!machine_net_ctx
) {
234 r
->out
.error_string
= NULL
;
235 talloc_free(samsync_ctx
);
236 return NT_STATUS_NO_MEMORY
;
238 *machine_net_ctx
= *ctx
;
239 machine_net_ctx
->cred
= machine_account
;
241 /* connect to the NETLOGON pipe of the PDC */
242 nt_status
= libnet_RpcConnect(machine_net_ctx
, samsync_ctx
, c
);
243 if (!NT_STATUS_IS_OK(nt_status
)) {
244 if (r
->in
.binding_string
) {
245 r
->out
.error_string
= talloc_asprintf(mem_ctx
,
246 "Connection to NETLOGON pipe of DC %s failed: %s",
247 r
->in
.binding_string
, c
->out
.error_string
);
249 r
->out
.error_string
= talloc_asprintf(mem_ctx
,
250 "Connection to NETLOGON pipe of DC for %s failed: %s",
251 c
->in
.name
, c
->out
.error_string
);
253 talloc_free(samsync_ctx
);
257 /* This makes a new pipe, on which we can do schannel. We
258 * should do this in the RpcConnect code, but the abstaction
259 * layers do not suit yet */
261 nt_status
= dcerpc_secondary_connection(c
->out
.dcerpc_pipe
, &p
,
262 c
->out
.dcerpc_pipe
->binding
);
264 if (!NT_STATUS_IS_OK(nt_status
)) {
265 r
->out
.error_string
= talloc_asprintf(mem_ctx
,
266 "Secondary connection to NETLOGON pipe of DC %s failed: %s",
267 dcerpc_server_name(p
), nt_errstr(nt_status
));
268 talloc_free(samsync_ctx
);
272 nt_status
= dcerpc_bind_auth_schannel(samsync_ctx
, p
, &dcerpc_table_netlogon
,
273 machine_account
, DCERPC_AUTH_LEVEL_PRIVACY
);
275 if (!NT_STATUS_IS_OK(nt_status
)) {
276 r
->out
.error_string
= talloc_asprintf(mem_ctx
,
277 "SCHANNEL authentication to NETLOGON pipe of DC %s failed: %s",
278 dcerpc_server_name(p
), nt_errstr(nt_status
));
279 talloc_free(samsync_ctx
);
283 state
= talloc(samsync_ctx
, struct libnet_SamSync_state
);
285 r
->out
.error_string
= NULL
;
286 talloc_free(samsync_ctx
);
290 state
->domain_name
= c
->out
.domain_name
;
291 state
->domain_sid
= c
->out
.domain_sid
;
292 state
->realm
= c
->out
.realm
;
293 state
->domain_guid
= c
->out
.guid
;
294 state
->machine_net_ctx
= machine_net_ctx
;
295 state
->netlogon_pipe
= p
;
297 /* initialise the callback layer. It may wish to contact the
298 * server with ldap, now we know the name */
302 nt_status
= r
->in
.init_fn(samsync_ctx
,
306 if (!NT_STATUS_IS_OK(nt_status
)) {
307 r
->out
.error_string
= talloc_steal(mem_ctx
, error_string
);
308 talloc_free(samsync_ctx
);
313 /* get NETLOGON credentails */
315 nt_status
= dcerpc_schannel_creds(p
->conn
->security_state
.generic_state
, samsync_ctx
, &creds
);
316 if (!NT_STATUS_IS_OK(nt_status
)) {
317 r
->out
.error_string
= talloc_strdup(mem_ctx
, "Could not obtain NETLOGON credentials from DCERPC/GENSEC layer");
318 talloc_free(samsync_ctx
);
322 /* Setup details for the synchronisation */
323 dbsync
.in
.logon_server
= talloc_asprintf(samsync_ctx
, "\\\\%s", dcerpc_server_name(p
));
324 dbsync
.in
.computername
= cli_credentials_get_workstation(machine_account
);
325 dbsync
.in
.preferredmaximumlength
= (uint32_t)-1;
326 ZERO_STRUCT(dbsync
.in
.return_authenticator
);
328 for (i
=0;i
< ARRAY_SIZE(database_ids
); i
++) {
329 dbsync
.in
.sync_context
= 0;
330 dbsync
.in
.database_id
= database_ids
[i
];
334 loop_ctx
= talloc_named(samsync_ctx
, 0, "DatabaseSync loop context");
335 creds_client_authenticator(creds
, &dbsync
.in
.credential
);
337 dbsync_nt_status
= dcerpc_netr_DatabaseSync(p
, loop_ctx
, &dbsync
);
338 if (!NT_STATUS_IS_OK(dbsync_nt_status
) &&
339 !NT_STATUS_EQUAL(dbsync_nt_status
, STATUS_MORE_ENTRIES
)) {
340 r
->out
.error_string
= talloc_asprintf(mem_ctx
, "DatabaseSync failed - %s", nt_errstr(nt_status
));
341 talloc_free(samsync_ctx
);
345 if (!creds_client_check(creds
, &dbsync
.out
.return_authenticator
.cred
)) {
346 r
->out
.error_string
= talloc_strdup(mem_ctx
, "Credential chaining on incoming DatabaseSync failed");
347 talloc_free(samsync_ctx
);
348 return NT_STATUS_ACCESS_DENIED
;
351 dbsync
.in
.sync_context
= dbsync
.out
.sync_context
;
353 /* For every single remote 'delta' entry: */
354 for (d
=0; d
< dbsync
.out
.delta_enum_array
->num_deltas
; d
++) {
355 char *error_string
= NULL
;
356 delta_ctx
= talloc_named(loop_ctx
, 0, "DatabaseSync delta context");
357 /* 'Fix' elements, by decrypting and
358 * de-obfuscating the data */
359 nt_status
= fix_delta(delta_ctx
,
362 dbsync
.in
.database_id
,
363 &dbsync
.out
.delta_enum_array
->delta_enum
[d
],
365 if (!NT_STATUS_IS_OK(nt_status
)) {
366 r
->out
.error_string
= talloc_steal(mem_ctx
, error_string
);
367 talloc_free(samsync_ctx
);
371 /* Now call the callback. This will
372 * do something like print the data or
374 nt_status
= r
->in
.delta_fn(delta_ctx
,
376 dbsync
.in
.database_id
,
377 &dbsync
.out
.delta_enum_array
->delta_enum
[d
],
379 if (!NT_STATUS_IS_OK(nt_status
)) {
380 r
->out
.error_string
= talloc_steal(mem_ctx
, error_string
);
381 talloc_free(samsync_ctx
);
384 talloc_free(delta_ctx
);
386 talloc_free(loop_ctx
);
387 } while (NT_STATUS_EQUAL(dbsync_nt_status
, STATUS_MORE_ENTRIES
));
389 if (!NT_STATUS_IS_OK(dbsync_nt_status
)) {
390 r
->out
.error_string
= talloc_asprintf(mem_ctx
, "libnet_SamSync_netlogon failed: unexpected inconsistancy. Should not get error %s here", nt_errstr(nt_status
));
391 talloc_free(samsync_ctx
);
392 return dbsync_nt_status
;
394 nt_status
= NT_STATUS_OK
;
396 talloc_free(samsync_ctx
);