2 Unix SMB/CIFS implementation.
4 CLDAP server - netlogon handling
6 Copyright (C) Andrew Tridgell 2005
7 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 3 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
24 #include "lib/ldb/include/ldb.h"
25 #include "lib/ldb/include/ldb_errors.h"
26 #include "lib/events/events.h"
27 #include "lib/socket/socket.h"
28 #include "smbd/service_task.h"
29 #include "cldap_server/cldap_server.h"
30 #include "librpc/gen_ndr/ndr_misc.h"
31 #include "libcli/ldap/ldap_ndr.h"
32 #include "libcli/security/security.h"
33 #include "dsdb/samdb/samdb.h"
34 #include "auth/auth.h"
36 #include "system/network.h"
37 #include "lib/socket/netif.h"
38 #include "param/param.h"
40 fill in the cldap netlogon union for a given version
42 NTSTATUS
fill_netlogon_samlogon_response(struct ldb_context
*sam_ctx
,
45 const char *netbios_domain
,
46 struct dom_sid
*domain_sid
,
47 const char *domain_guid
,
49 uint32_t acct_control
,
50 const char *src_address
,
52 struct loadparm_context
*lp_ctx
,
53 struct netlogon_samlogon_response
*netlogon
)
55 const char *ref_attrs
[] = {"nETBIOSName", "dnsRoot", "ncName", NULL
};
56 const char *dom_attrs
[] = {"objectGUID", NULL
};
57 const char *none_attrs
[] = {NULL
};
58 struct ldb_result
*ref_res
= NULL
, *dom_res
= NULL
, *user_res
= NULL
;
60 const char **services
= lp_server_services(lp_ctx
);
63 struct GUID domain_uuid
;
65 const char *dns_domain
;
66 const char *pdc_dns_name
;
68 const char *server_site
;
69 const char *client_site
;
71 struct ldb_dn
*partitions_basedn
;
72 struct interface
*ifaces
;
76 partitions_basedn
= samdb_partitions_dn(sam_ctx
, mem_ctx
);
78 /* the domain has an optional trailing . */
79 if (domain
&& domain
[strlen(domain
)-1] == '.') {
80 domain
= talloc_strndup(mem_ctx
, domain
, strlen(domain
)-1);
84 struct ldb_dn
*dom_dn
;
85 /* try and find the domain */
87 ret
= ldb_search(sam_ctx
, mem_ctx
, &ref_res
,
88 partitions_basedn
, LDB_SCOPE_ONELEVEL
,
90 "(&(&(objectClass=crossRef)(dnsRoot=%s))(nETBIOSName=*))",
91 ldb_binary_encode_string(mem_ctx
, domain
));
93 if (ret
!= LDB_SUCCESS
) {
94 DEBUG(2,("Unable to find referece to '%s' in sam: %s\n",
96 ldb_errstring(sam_ctx
)));
97 return NT_STATUS_NO_SUCH_DOMAIN
;
98 } else if (ref_res
->count
== 1) {
99 dom_dn
= ldb_msg_find_attr_as_dn(sam_ctx
, mem_ctx
, ref_res
->msgs
[0], "ncName");
101 return NT_STATUS_NO_SUCH_DOMAIN
;
103 ret
= ldb_search(sam_ctx
, mem_ctx
, &dom_res
,
104 dom_dn
, LDB_SCOPE_BASE
, dom_attrs
,
105 "objectClass=domain");
106 if (ret
!= LDB_SUCCESS
) {
107 DEBUG(2,("Error finding domain '%s'/'%s' in sam: %s\n", domain
, ldb_dn_get_linearized(dom_dn
), ldb_errstring(sam_ctx
)));
108 return NT_STATUS_NO_SUCH_DOMAIN
;
110 if (dom_res
->count
!= 1) {
111 DEBUG(2,("Error finding domain '%s'/'%s' in sam\n", domain
, ldb_dn_get_linearized(dom_dn
)));
112 return NT_STATUS_NO_SUCH_DOMAIN
;
114 } else if (ref_res
->count
> 1) {
115 talloc_free(ref_res
);
116 return NT_STATUS_NO_SUCH_DOMAIN
;
120 if (netbios_domain
) {
121 struct ldb_dn
*dom_dn
;
122 /* try and find the domain */
124 ret
= ldb_search(sam_ctx
, mem_ctx
, &ref_res
,
125 partitions_basedn
, LDB_SCOPE_ONELEVEL
,
127 "(&(objectClass=crossRef)(ncName=*)(nETBIOSName=%s))",
128 ldb_binary_encode_string(mem_ctx
, netbios_domain
));
130 if (ret
!= LDB_SUCCESS
) {
131 DEBUG(2,("Unable to find referece to '%s' in sam: %s\n",
133 ldb_errstring(sam_ctx
)));
134 return NT_STATUS_NO_SUCH_DOMAIN
;
135 } else if (ref_res
->count
== 1) {
136 dom_dn
= ldb_msg_find_attr_as_dn(sam_ctx
, mem_ctx
, ref_res
->msgs
[0], "ncName");
138 return NT_STATUS_NO_SUCH_DOMAIN
;
140 ret
= ldb_search(sam_ctx
, mem_ctx
, &dom_res
,
141 dom_dn
, LDB_SCOPE_BASE
, dom_attrs
,
142 "objectClass=domain");
143 if (ret
!= LDB_SUCCESS
) {
144 DEBUG(2,("Error finding domain '%s'/'%s' in sam: %s\n", domain
, ldb_dn_get_linearized(dom_dn
), ldb_errstring(sam_ctx
)));
145 return NT_STATUS_NO_SUCH_DOMAIN
;
147 if (dom_res
->count
!= 1) {
148 DEBUG(2,("Error finding domain '%s'/'%s' in sam\n", domain
, ldb_dn_get_linearized(dom_dn
)));
149 return NT_STATUS_NO_SUCH_DOMAIN
;
151 } else if (ref_res
->count
> 1) {
152 talloc_free(ref_res
);
153 return NT_STATUS_NO_SUCH_DOMAIN
;
157 if ((dom_res
== NULL
|| dom_res
->count
== 0) && (domain_guid
|| domain_sid
)) {
161 struct GUID binary_guid
;
162 struct ldb_val guid_val
;
163 enum ndr_err_code ndr_err
;
165 /* By this means, we ensure we don't have funny stuff in the GUID */
167 status
= GUID_from_string(domain_guid
, &binary_guid
);
168 if (!NT_STATUS_IS_OK(status
)) {
172 /* And this gets the result into the binary format we want anyway */
173 ndr_err
= ndr_push_struct_blob(&guid_val
, mem_ctx
, NULL
, &binary_guid
,
174 (ndr_push_flags_fn_t
)ndr_push_GUID
);
175 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err
)) {
176 return NT_STATUS_INVALID_PARAMETER
;
178 ret
= ldb_search(sam_ctx
, mem_ctx
, &dom_res
,
179 NULL
, LDB_SCOPE_SUBTREE
,
181 "(&(objectCategory=DomainDNS)(objectGUID=%s))",
182 ldb_binary_encode(mem_ctx
, guid_val
));
183 } else { /* domain_sid case */
185 struct ldb_val sid_val
;
186 enum ndr_err_code ndr_err
;
188 /* Rather than go via the string, just push into the NDR form */
189 ndr_err
= ndr_push_struct_blob(&sid_val
, mem_ctx
, NULL
, &sid
,
190 (ndr_push_flags_fn_t
)ndr_push_dom_sid
);
191 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err
)) {
192 return NT_STATUS_INVALID_PARAMETER
;
195 ret
= ldb_search(sam_ctx
, mem_ctx
, &dom_res
,
196 NULL
, LDB_SCOPE_SUBTREE
,
198 "(&(objectCategory=DomainDNS)(objectSID=%s))",
199 ldb_binary_encode(mem_ctx
, sid_val
));
202 if (ret
!= LDB_SUCCESS
) {
203 DEBUG(2,("Unable to find referece to GUID '%s' or SID %s in sam: %s\n",
204 domain_guid
, dom_sid_string(mem_ctx
, domain_sid
),
205 ldb_errstring(sam_ctx
)));
206 return NT_STATUS_NO_SUCH_DOMAIN
;
207 } else if (dom_res
->count
== 1) {
208 /* try and find the domain */
209 ret
= ldb_search(sam_ctx
, mem_ctx
, &ref_res
,
210 partitions_basedn
, LDB_SCOPE_ONELEVEL
,
212 "(&(objectClass=crossRef)(ncName=%s))",
213 ldb_dn_get_linearized(dom_res
->msgs
[0]->dn
));
215 if (ret
!= LDB_SUCCESS
) {
216 DEBUG(2,("Unable to find referece to '%s' in sam: %s\n",
217 ldb_dn_get_linearized(dom_res
->msgs
[0]->dn
),
218 ldb_errstring(sam_ctx
)));
219 return NT_STATUS_NO_SUCH_DOMAIN
;
221 } else if (ref_res
->count
!= 1) {
222 DEBUG(2,("Unable to find referece to '%s' in sam\n",
223 ldb_dn_get_linearized(dom_res
->msgs
[0]->dn
)));
224 return NT_STATUS_NO_SUCH_DOMAIN
;
226 } else if (dom_res
->count
> 1) {
227 talloc_free(ref_res
);
228 return NT_STATUS_NO_SUCH_DOMAIN
;
233 if ((ref_res
== NULL
|| ref_res
->count
== 0)) {
234 DEBUG(2,("Unable to find domain reference with name %s or GUID {%s}\n", domain
, domain_guid
));
235 return NT_STATUS_NO_SUCH_DOMAIN
;
238 if ((dom_res
== NULL
|| dom_res
->count
== 0)) {
239 DEBUG(2,("Unable to find domain with name %s or GUID {%s}\n", domain
, domain_guid
));
240 return NT_STATUS_NO_SUCH_DOMAIN
;
243 /* work around different inputs for not-specified users */
248 /* Enquire about any valid username with just a CLDAP packet -
249 * if kerberos didn't also do this, the security folks would
252 /* Only allow some bits to be enquired: [MS-ATDS] 7.3.3.2 */
253 if (acct_control
== (uint32_t)-1) {
256 acct_control
= acct_control
& (ACB_TEMPDUP
| ACB_NORMAL
| ACB_DOMTRUST
| ACB_WSTRUST
| ACB_SVRTRUST
);
258 /* We must exclude disabled accounts, but otherwise do the bitwise match the client asked for */
259 ret
= ldb_search(sam_ctx
, mem_ctx
, &user_res
,
260 dom_res
->msgs
[0]->dn
, LDB_SCOPE_SUBTREE
,
262 "(&(objectClass=user)(samAccountName=%s)"
263 "(!(userAccountControl:" LDB_OID_COMPARATOR_AND
":=%u))"
264 "(userAccountControl:" LDB_OID_COMPARATOR_OR
":=%u))",
265 ldb_binary_encode_string(mem_ctx
, user
),
266 UF_ACCOUNTDISABLE
, samdb_acb2uf(acct_control
));
267 if (ret
!= LDB_SUCCESS
) {
268 DEBUG(2,("Unable to find referece to user '%s' with ACB 0x%8x under %s: %s\n",
269 user
, acct_control
, ldb_dn_get_linearized(dom_res
->msgs
[0]->dn
),
270 ldb_errstring(sam_ctx
)));
271 return NT_STATUS_NO_SUCH_USER
;
272 } else if (user_res
->count
== 1) {
283 NBT_SERVER_DS
| NBT_SERVER_TIMESERV
|
284 NBT_SERVER_CLOSEST
| NBT_SERVER_WRITABLE
|
285 NBT_SERVER_GOOD_TIMESERV
| DS_DNS_CONTROLLER
|
288 if (samdb_is_pdc(sam_ctx
)) {
289 server_type
|= NBT_SERVER_PDC
;
292 if (samdb_is_gc(sam_ctx
)) {
293 server_type
|= NBT_SERVER_GC
;
296 if (str_list_check(services
, "ldap")) {
297 server_type
|= NBT_SERVER_LDAP
;
300 if (str_list_check(services
, "kdc")) {
301 server_type
|= NBT_SERVER_KDC
;
304 if (ldb_dn_compare(ldb_get_root_basedn(sam_ctx
), ldb_get_default_basedn(sam_ctx
)) == 0) {
305 server_type
|= DS_DNS_FOREST
;
308 pdc_name
= talloc_asprintf(mem_ctx
, "\\\\%s", lp_netbios_name(lp_ctx
));
309 domain_uuid
= samdb_result_guid(dom_res
->msgs
[0], "objectGUID");
310 realm
= samdb_result_string(ref_res
->msgs
[0], "dnsRoot", lp_realm(lp_ctx
));
311 dns_domain
= samdb_result_string(ref_res
->msgs
[0], "dnsRoot", lp_realm(lp_ctx
));
312 pdc_dns_name
= talloc_asprintf(mem_ctx
, "%s.%s",
313 strlower_talloc(mem_ctx
,
314 lp_netbios_name(lp_ctx
)),
317 flatname
= samdb_result_string(ref_res
->msgs
[0], "nETBIOSName",
318 lp_workgroup(lp_ctx
));
319 /* FIXME: Hardcoded site names */
320 server_site
= "Default-First-Site-Name";
321 client_site
= "Default-First-Site-Name";
322 load_interfaces(mem_ctx
, lp_interfaces(lp_ctx
), &ifaces
);
323 pdc_ip
= iface_best_ip(ifaces
, src_address
);
325 ZERO_STRUCTP(netlogon
);
327 /* check if either of these bits is present */
328 if (version
& (NETLOGON_NT_VERSION_5EX
|NETLOGON_NT_VERSION_5EX_WITH_IP
)) {
329 uint32_t extra_flags
= 0;
330 netlogon
->ntver
= NETLOGON_NT_VERSION_5EX
;
332 /* could check if the user exists */
334 netlogon
->data
.nt5_ex
.command
= LOGON_SAM_LOGON_RESPONSE_EX
;
336 netlogon
->data
.nt5_ex
.command
= LOGON_SAM_LOGON_USER_UNKNOWN_EX
;
338 netlogon
->data
.nt5_ex
.server_type
= server_type
;
339 netlogon
->data
.nt5_ex
.domain_uuid
= domain_uuid
;
340 netlogon
->data
.nt5_ex
.forest
= realm
;
341 netlogon
->data
.nt5_ex
.dns_domain
= dns_domain
;
342 netlogon
->data
.nt5_ex
.pdc_dns_name
= pdc_dns_name
;
343 netlogon
->data
.nt5_ex
.domain
= flatname
;
344 netlogon
->data
.nt5_ex
.pdc_name
= lp_netbios_name(lp_ctx
);
345 netlogon
->data
.nt5_ex
.user_name
= user
;
346 netlogon
->data
.nt5_ex
.server_site
= server_site
;
347 netlogon
->data
.nt5_ex
.client_site
= client_site
;
349 if (version
& NETLOGON_NT_VERSION_5EX_WITH_IP
) {
350 /* Clearly this needs to be fixed up for IPv6 */
351 extra_flags
= NETLOGON_NT_VERSION_5EX_WITH_IP
;
352 netlogon
->data
.nt5_ex
.sockaddr
.sockaddr_family
= 2;
353 netlogon
->data
.nt5_ex
.sockaddr
.pdc_ip
= pdc_ip
;
354 netlogon
->data
.nt5_ex
.sockaddr
.remaining
= data_blob_talloc_zero(mem_ctx
, 8);
356 netlogon
->data
.nt5_ex
.nt_version
= NETLOGON_NT_VERSION_1
|NETLOGON_NT_VERSION_5EX
|extra_flags
;
357 netlogon
->data
.nt5_ex
.lmnt_token
= 0xFFFF;
358 netlogon
->data
.nt5_ex
.lm20_token
= 0xFFFF;
360 } else if (version
& NETLOGON_NT_VERSION_5
) {
361 netlogon
->ntver
= NETLOGON_NT_VERSION_5
;
363 /* could check if the user exists */
365 netlogon
->data
.nt5
.command
= LOGON_SAM_LOGON_RESPONSE
;
367 netlogon
->data
.nt5
.command
= LOGON_SAM_LOGON_USER_UNKNOWN
;
369 netlogon
->data
.nt5
.pdc_name
= pdc_name
;
370 netlogon
->data
.nt5
.user_name
= user
;
371 netlogon
->data
.nt5
.domain_name
= flatname
;
372 netlogon
->data
.nt5
.domain_uuid
= domain_uuid
;
373 netlogon
->data
.nt5
.forest
= realm
;
374 netlogon
->data
.nt5
.dns_domain
= dns_domain
;
375 netlogon
->data
.nt5
.pdc_dns_name
= pdc_dns_name
;
376 netlogon
->data
.nt5
.pdc_ip
= pdc_ip
;
377 netlogon
->data
.nt5
.server_type
= server_type
;
378 netlogon
->data
.nt5
.nt_version
= NETLOGON_NT_VERSION_1
|NETLOGON_NT_VERSION_5
;
379 netlogon
->data
.nt5
.lmnt_token
= 0xFFFF;
380 netlogon
->data
.nt5
.lm20_token
= 0xFFFF;
382 } else /* (version & NETLOGON_NT_VERSION_1) and all other cases */ {
383 netlogon
->ntver
= NETLOGON_NT_VERSION_1
;
384 /* could check if the user exists */
386 netlogon
->data
.nt4
.command
= LOGON_SAM_LOGON_RESPONSE
;
388 netlogon
->data
.nt4
.command
= LOGON_SAM_LOGON_USER_UNKNOWN
;
390 netlogon
->data
.nt4
.server
= pdc_name
;
391 netlogon
->data
.nt4
.user_name
= user
;
392 netlogon
->data
.nt4
.domain
= flatname
;
393 netlogon
->data
.nt4
.nt_version
= NETLOGON_NT_VERSION_1
;
394 netlogon
->data
.nt4
.lmnt_token
= 0xFFFF;
395 netlogon
->data
.nt4
.lm20_token
= 0xFFFF;
403 handle incoming cldap requests
405 void cldapd_netlogon_request(struct cldap_socket
*cldap
,
407 struct ldb_parse_tree
*tree
,
408 struct socket_address
*src
)
410 struct cldapd_server
*cldapd
= talloc_get_type(cldap
->incoming
.private, struct cldapd_server
);
412 const char *domain
= NULL
;
413 const char *host
= NULL
;
414 const char *user
= NULL
;
415 const char *domain_guid
= NULL
;
416 const char *domain_sid
= NULL
;
417 int acct_control
= -1;
419 struct netlogon_samlogon_response netlogon
;
420 NTSTATUS status
= NT_STATUS_INVALID_PARAMETER
;
422 TALLOC_CTX
*tmp_ctx
= talloc_new(cldap
);
424 if (tree
->operation
!= LDB_OP_AND
) goto failed
;
426 /* extract the query elements */
427 for (i
=0;i
<tree
->u
.list
.num_elements
;i
++) {
428 struct ldb_parse_tree
*t
= tree
->u
.list
.elements
[i
];
429 if (t
->operation
!= LDB_OP_EQUALITY
) goto failed
;
430 if (strcasecmp(t
->u
.equality
.attr
, "DnsDomain") == 0) {
431 domain
= talloc_strndup(tmp_ctx
,
432 (const char *)t
->u
.equality
.value
.data
,
433 t
->u
.equality
.value
.length
);
435 if (strcasecmp(t
->u
.equality
.attr
, "Host") == 0) {
436 host
= talloc_strndup(tmp_ctx
,
437 (const char *)t
->u
.equality
.value
.data
,
438 t
->u
.equality
.value
.length
);
440 if (strcasecmp(t
->u
.equality
.attr
, "DomainGuid") == 0) {
443 enc_status
= ldap_decode_ndr_GUID(tmp_ctx
,
444 t
->u
.equality
.value
, &guid
);
445 if (NT_STATUS_IS_OK(enc_status
)) {
446 domain_guid
= GUID_string(tmp_ctx
, &guid
);
449 if (strcasecmp(t
->u
.equality
.attr
, "DomainSid") == 0) {
450 domain_sid
= talloc_strndup(tmp_ctx
,
451 (const char *)t
->u
.equality
.value
.data
,
452 t
->u
.equality
.value
.length
);
454 if (strcasecmp(t
->u
.equality
.attr
, "User") == 0) {
455 user
= talloc_strndup(tmp_ctx
,
456 (const char *)t
->u
.equality
.value
.data
,
457 t
->u
.equality
.value
.length
);
459 if (strcasecmp(t
->u
.equality
.attr
, "NtVer") == 0 &&
460 t
->u
.equality
.value
.length
== 4) {
461 version
= IVAL(t
->u
.equality
.value
.data
, 0);
463 if (strcasecmp(t
->u
.equality
.attr
, "AAC") == 0 &&
464 t
->u
.equality
.value
.length
== 4) {
465 acct_control
= IVAL(t
->u
.equality
.value
.data
, 0);
469 if (domain_guid
== NULL
&& domain
== NULL
) {
470 domain
= lp_realm(cldapd
->task
->lp_ctx
);
477 DEBUG(5,("cldap netlogon query domain=%s host=%s user=%s version=%d guid=%s\n",
478 domain
, host
, user
, version
, domain_guid
));
480 status
= fill_netlogon_samlogon_response(cldapd
->samctx
, tmp_ctx
, domain
, NULL
, NULL
, domain_guid
,
481 user
, acct_control
, src
->addr
,
482 version
, cldapd
->task
->lp_ctx
, &netlogon
);
483 if (!NT_STATUS_IS_OK(status
)) {
487 status
= cldap_netlogon_reply(cldap
, message_id
, src
, version
,
489 if (!NT_STATUS_IS_OK(status
)) {
493 talloc_free(tmp_ctx
);
497 DEBUG(2,("cldap netlogon query failed domain=%s host=%s version=%d - %s\n",
498 domain
, host
, version
, nt_errstr(status
)));
499 talloc_free(tmp_ctx
);
500 cldap_empty_reply(cldap
, message_id
, src
);