4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
27 * This module handles the primary domain controller location protocol.
28 * The document claims to be version 1.15 of the browsing protocol. It also
29 * claims to specify the mailslot protocol.
31 * The NETLOGON protocol uses \MAILSLOT\NET mailslots. The protocol
32 * specification is incomplete, contains errors and is out-of-date but
33 * it does provide some useful background information. The document
34 * doesn't mention the NETLOGON_SAMLOGON version of the protocol.
40 #include <arpa/inet.h>
43 #include <smbsrv/mailslot.h>
44 #include <smbsrv/libsmbns.h>
45 #include <smbns_browser.h>
46 #include <smbns_netbios.h>
48 static void smb_netlogon_query(struct name_entry
*server
, char *mailbox
,
51 static void smb_netlogon_samlogon(struct name_entry
*, char *,
54 static void smb_netlogon_send(struct name_entry
*name
, char *domain
,
55 unsigned char *buffer
, int count
);
57 static void smb_netlogon_rdc_rsp(char *src_name
, uint32_t src_ipaddr
);
58 static int smb_better_dc(uint32_t cur_ip
, uint32_t new_ip
);
62 * Temporary. It should be removed once NBTD is integrated.
64 extern smb_ntdomain_t ntdomain_info
;
65 extern mutex_t ntdomain_mtx
;
66 extern cond_t ntdomain_cv
;
69 * smb_netlogon_request
71 * This is the entry point locating the resource domain PDC. A netlogon
72 * request is sent using the specified protocol on the specified network.
73 * Note that we need to know the domain SID in order to use the samlogon
76 * Netlogon responses are received asynchronously and eventually handled
77 * in smb_netlogon_receive.
80 smb_netlogon_request(struct name_entry
*server
, char *domain
)
83 smb_sid_t
*sid
= NULL
;
84 int protocol
= NETLOGON_PROTO_NETLOGON
;
86 if (domain
== NULL
|| *domain
== '\0')
89 (void) mutex_lock(&ntdomain_mtx
);
90 (void) strlcpy(ntdomain_info
.n_domain
, domain
,
91 sizeof (ntdomain_info
.n_domain
));
92 (void) mutex_unlock(&ntdomain_mtx
);
94 smb_config_getdomaininfo(di
.di_nbname
, NULL
, di
.di_sid
, NULL
, NULL
);
95 if (smb_strcasecmp(di
.di_nbname
, domain
, 0) == 0) {
96 if ((sid
= smb_sid_fromstr(di
.di_sid
)) != NULL
)
97 protocol
= NETLOGON_PROTO_SAMLOGON
;
100 if (protocol
== NETLOGON_PROTO_SAMLOGON
)
101 smb_netlogon_samlogon(server
, MAILSLOT_NETLOGON_SAMLOGON_RDC
,
104 smb_netlogon_query(server
, MAILSLOT_NETLOGON_RDC
, domain
);
110 * smb_netlogon_receive
112 * This is where we handle all incoming NetLogon messages. Currently, we
113 * ignore requests from anyone else. We are only interested in responses
114 * to our own requests. The NetLogonResponse provides the name of the PDC.
115 * If we don't already have a controller name, we use the name provided
116 * in the message. Otherwise we use the name already in the environment.
119 smb_netlogon_receive(struct datagram
*datagram
,
124 struct netlogon_opt
{
128 { MAILSLOT_NETLOGON_RDC
, smb_netlogon_rdc_rsp
},
129 { MAILSLOT_NETLOGON_SAMLOGON_RDC
, smb_netlogon_rdc_rsp
},
133 unsigned short opcode
;
134 char src_name
[SMB_PI_MAX_HOST
];
135 smb_wchar_t unicode_src_name
[SMB_PI_MAX_HOST
];
144 src_ipaddr
= datagram
->src
.addr_list
.sin
.sin_addr
.s_addr
;
147 * The datagram->src.name is in oem codepage format.
148 * Therefore, we need to convert it to unicode and
149 * store it in multi-bytes format.
151 (void) oemtoucs(unicode_src_name
, (char *)datagram
->src
.name
,
152 SMB_PI_MAX_HOST
, OEM_CPG_850
);
153 (void) smb_wcstombs(src_name
, unicode_src_name
, SMB_PI_MAX_HOST
);
155 (void) trim_whitespace(src_name
);
157 (void) inet_ntop(AF_INET
, (const void *)(&src_ipaddr
), ipstr
,
159 syslog(LOG_DEBUG
, "NetLogonReceive: src=%s [%s], mbx=%s",
160 src_name
, ipstr
, mailbox
);
162 smb_msgbuf_init(&mb
, data
, datalen
, 0);
164 if (smb_msgbuf_decode(&mb
, "w", &opcode
) < 0) {
165 syslog(LOG_ERR
, "NetLogonReceive: decode error");
166 smb_msgbuf_term(&mb
);
171 case LOGON_PRIMARY_RESPONSE
:
174 * PDC name (MBS), PDC name (Unicode), Domain name (unicode)
176 rc
= smb_msgbuf_decode(&mb
, "sUU", &junk
, &primary
, &domain
);
179 "NetLogonResponse: opcode %d decode error",
181 smb_msgbuf_term(&mb
);
186 case LOGON_SAM_LOGON_RESPONSE
:
187 case LOGON_SAM_USER_UNKNOWN
:
190 * PDC name, User name, Domain name (all unicode)
192 rc
= smb_msgbuf_decode(&mb
, "UUU", &primary
, &junk
, &domain
);
195 "NetLogonResponse: opcode %d decode error",
197 smb_msgbuf_term(&mb
);
202 * skip past the "\\" prefix
204 primary
+= strspn(primary
, "\\");
209 * We don't respond to PDC discovery requests.
211 syslog(LOG_DEBUG
, "NetLogonReceive: opcode 0x%04x", opcode
);
212 smb_msgbuf_term(&mb
);
216 if (domain
== NULL
|| primary
== NULL
) {
217 syslog(LOG_ERR
, "NetLogonResponse: malformed packet");
218 smb_msgbuf_term(&mb
);
222 syslog(LOG_DEBUG
, "DC Offer Domain=%s PDC=%s From=%s",
223 domain
, primary
, src_name
);
225 (void) mutex_lock(&ntdomain_mtx
);
226 if (strcasecmp(domain
, ntdomain_info
.n_domain
)) {
227 syslog(LOG_DEBUG
, "NetLogonResponse: other domain "
228 "%s, requested %s", domain
, ntdomain_info
.n_domain
);
229 smb_msgbuf_term(&mb
);
230 (void) mutex_unlock(&ntdomain_mtx
);
233 (void) mutex_unlock(&ntdomain_mtx
);
235 for (i
= 0; i
< sizeof (netlogon_opt
)/sizeof (netlogon_opt
[0]); ++i
) {
236 if (strcasecmp(netlogon_opt
[i
].mailslot
, mailbox
) == 0) {
237 syslog(LOG_DEBUG
, "NetLogonReceive: %s", mailbox
);
238 (*netlogon_opt
[i
].handler
)(primary
, src_ipaddr
);
239 smb_msgbuf_term(&mb
);
244 syslog(LOG_DEBUG
, "NetLogonReceive[%s]: unknown mailslot", mailbox
);
245 smb_msgbuf_term(&mb
);
253 * Build and send a LOGON_PRIMARY_QUERY to the MAILSLOT_NETLOGON. At some
254 * point we should receive a LOGON_PRIMARY_RESPONSE in the mailslot we
255 * specify in the request.
257 * struct NETLOGON_QUERY {
258 * unsigned short Opcode; # LOGON_PRIMARY_QUERY
259 * char ComputerName[]; # ASCII hostname. The response
260 * # is sent to <ComputerName>(00).
261 * char MailslotName[]; # MAILSLOT_NETLOGON
262 * char Pad[]; # Pad to short
263 * wchar_t ComputerName[] # UNICODE hostname
264 * DWORD NT_Version; # 0x00000001
265 * WORD LmNTToken; # 0xffff
266 * WORD Lm20Token; # 0xffff
270 smb_netlogon_query(struct name_entry
*server
,
275 int offset
, announce_len
, data_length
, name_lengths
;
276 unsigned char buffer
[MAX_DATAGRAM_LENGTH
];
277 char hostname
[NETBIOS_NAME_SZ
];
279 if (smb_getnetbiosname(hostname
, sizeof (hostname
)) != 0)
282 name_lengths
= strlen(mailbox
)+1+strlen(hostname
)+1;
285 * The (name_lengths & 1) part is to word align the name_lengths
286 * before the wc equiv strlen and the "+ 2" is to cover the two
287 * zero bytes that terminate the wchar string.
289 data_length
= sizeof (short) + name_lengths
+ (name_lengths
& 1) +
290 smb_wcequiv_strlen(hostname
) + 2 + sizeof (long) + sizeof (short) +
293 offset
= smb_browser_load_transact_header(buffer
,
294 sizeof (buffer
), data_length
, ONE_WAY_TRANSACTION
,
300 smb_msgbuf_init(&mb
, buffer
+ offset
, sizeof (buffer
) - offset
, 0);
302 announce_len
= smb_msgbuf_encode(&mb
, "wssUlww",
303 (short)LOGON_PRIMARY_QUERY
,
311 if (announce_len
<= 0) {
312 smb_msgbuf_term(&mb
);
313 syslog(LOG_ERR
, "NetLogonQuery: encode error");
317 smb_netlogon_send(server
, domain
, buffer
, offset
+ announce_len
);
318 smb_msgbuf_term(&mb
);
323 * smb_netlogon_samlogon
325 * The SamLogon version of the NetLogon request uses the workstation trust
326 * account and, I think, may be a prerequisite to the challenge/response
327 * netr authentication. The trust account username is the hostname with a
328 * $ appended. The mailslot for this request is MAILSLOT_NTLOGON. At some
329 * we should receive a LOGON_SAM_LOGON_RESPONSE in the mailslot we
330 * specify in the request.
332 * struct NETLOGON_SAM_LOGON {
333 * unsigned short Opcode; # LOGON_SAM_LOGON_REQUEST
334 * unsigned short RequestCount; # 0
335 * wchar_t UnicodeComputerName; # hostname
336 * wchar_t UnicodeUserName; # hostname$
337 * char *MailslotName; # response mailslot
338 * DWORD AllowableAccountControlBits; # 0x80 = WorkstationTrustAccount
339 * DWORD DomainSidSize; # domain sid length in bytes
340 * BYTE *DomainSid; # domain sid
341 * uint32_t NT_Version; # 0x00000001
342 * unsigned short LmNTToken; # 0xffff
343 * unsigned short Lm20Token; # 0xffff
347 smb_netlogon_samlogon(struct name_entry
*server
,
350 smb_sid_t
*domain_sid
)
353 unsigned domain_sid_len
;
355 unsigned char buffer
[MAX_DATAGRAM_LENGTH
];
360 char hostname
[NETBIOS_NAME_SZ
];
362 syslog(LOG_DEBUG
, "NetLogonSamLogonReq: %s", domain
);
364 if (smb_getnetbiosname(hostname
, sizeof (hostname
)) != 0)
368 * The username will be the trust account name on the PDC.
370 name_length
= strlen(hostname
) + 2;
371 username
= alloca(name_length
);
372 (void) snprintf(username
, name_length
, "%s$", hostname
);
374 domain_sid_len
= smb_sid_len(domain_sid
);
376 * Add 2 to wide-char equivalent strlen to cover the
377 * two zero bytes that terminate the wchar string.
379 name_length
= strlen(mailbox
)+1;
381 data_length
= sizeof (short)
383 + smb_wcequiv_strlen(hostname
) + 2
384 + smb_wcequiv_strlen(username
) + 2
388 + domain_sid_len
+ 3 /* padding */
393 offset
= smb_browser_load_transact_header(buffer
,
394 sizeof (buffer
), data_length
, ONE_WAY_TRANSACTION
,
398 syslog(LOG_ERR
, "NetLogonSamLogonReq: header error");
403 * The domain SID is padded with 3 leading zeros.
405 smb_msgbuf_init(&mb
, buffer
+ offset
, sizeof (buffer
) - offset
, 0);
406 announce_len
= smb_msgbuf_encode(&mb
, "wwUUsll3.#clww",
407 (short)LOGON_SAM_LOGON_REQUEST
,
408 0, /* RequestCount */
409 hostname
, /* UnicodeComputerName */
410 username
, /* UnicodeUserName */
411 mailbox
, /* MailslotName */
412 0x00000080, /* AllowableAccountControlBits */
413 domain_sid_len
, /* DomainSidSize */
414 domain_sid_len
, domain_sid
, /* DomainSid */
415 0x00000001, /* NT_Version */
416 0xffff, /* LmNTToken */
417 0xffff); /* Lm20Token */
419 if (announce_len
<= 0) {
420 syslog(LOG_ERR
, "NetLogonSamLogonReq: encode error");
421 smb_msgbuf_term(&mb
);
425 smb_netlogon_send(server
, domain
, buffer
, offset
+ announce_len
);
426 smb_msgbuf_term(&mb
);
431 * Send a query for each version of the protocol.
434 smb_netlogon_send(struct name_entry
*name
,
436 unsigned char *buffer
,
439 static char suffix
[] = { 0x1B, 0x1C };
440 struct name_entry dname
;
441 struct name_entry
*dest
;
442 struct name_entry
*dest_dup
;
445 for (i
= 0; i
< sizeof (suffix
)/sizeof (suffix
[0]); i
++) {
446 smb_init_name_struct((unsigned char *)domain
, suffix
[i
],
447 0, 0, 0, 0, 0, &dname
);
449 syslog(LOG_DEBUG
, "SmbNetlogonSend");
450 smb_netbios_name_logf(&dname
);
451 if ((dest
= smb_name_find_name(&dname
)) != 0) {
452 dest_dup
= smb_netbios_name_dup(dest
, 1);
453 smb_name_unlock_name(dest
);
455 (void) smb_netbios_datagram_send(name
,
456 dest_dup
, buffer
, count
);
461 "SmbNetlogonSend: could not find %s<0x%X>",
468 * smb_netlogon_rdc_rsp
470 * This is where we process netlogon responses for the resource domain.
471 * The src_name is the real name of the remote machine.
474 smb_netlogon_rdc_rsp(char *src_name
, uint32_t src_ipaddr
)
476 static int initialized
= 0;
478 uint32_t prefer_ipaddr
;
479 char ipstr
[INET_ADDRSTRLEN
];
480 char srcip
[INET_ADDRSTRLEN
];
483 (void) inet_ntop(AF_INET
, &src_ipaddr
, srcip
, INET_ADDRSTRLEN
);
485 rc
= smb_config_getstr(SMB_CI_DOMAIN_SRV
, ipstr
, INET_ADDRSTRLEN
);
486 if (rc
== SMBD_SMF_OK
) {
487 rc
= inet_pton(AF_INET
, ipstr
, &prefer_ipaddr
);
492 syslog(LOG_DEBUG
, "SMB DC Preference: %s", ipstr
);
497 (void) mutex_lock(&ntdomain_mtx
);
498 syslog(LOG_DEBUG
, "DC Offer [%s]: %s [%s]",
499 ntdomain_info
.n_domain
, src_name
, srcip
);
501 if (ntdomain_info
.n_ipaddr
!= 0) {
502 if (prefer_ipaddr
!= 0 &&
503 prefer_ipaddr
== ntdomain_info
.n_ipaddr
) {
504 syslog(LOG_DEBUG
, "DC for %s: %s [%s]",
505 ntdomain_info
.n_domain
, src_name
, srcip
);
506 (void) mutex_unlock(&ntdomain_mtx
);
510 ipaddr
= ntdomain_info
.n_ipaddr
;
514 if (smb_better_dc(ipaddr
, src_ipaddr
) ||
515 (prefer_ipaddr
!= 0 && prefer_ipaddr
== src_ipaddr
)) {
517 (void) strlcpy(ntdomain_info
.n_name
, src_name
,
519 ntdomain_info
.n_ipaddr
= src_ipaddr
;
520 (void) cond_broadcast(&ntdomain_cv
);
521 syslog(LOG_DEBUG
, "DC discovered for %s: %s [%s]",
522 ntdomain_info
.n_domain
, src_name
, srcip
);
524 (void) mutex_unlock(&ntdomain_mtx
);
528 smb_better_dc(uint32_t cur_ip
, uint32_t new_ip
)
533 * If we don't have any current DC,
534 * then use the new one of course.
540 * see if there is a DC in the
544 ipaddr
.a_family
= AF_INET
;
545 ipaddr
.a_ipv4
= cur_ip
;
546 if (smb_nic_is_same_subnet(&ipaddr
))
549 ipaddr
.a_family
= AF_INET
;
550 ipaddr
.a_ipv4
= new_ip
;
551 if (smb_nic_is_same_subnet(&ipaddr
))
554 * Otherwise, just keep the old one.