dmake: do not set MAKEFLAGS=k
[unleashed/tickless.git] / usr / src / lib / smbsrv / libsmbns / common / smbns_netlogon.c
blobd0ebbfbe3812acc6f618ddacab23d964642841ae
1 /*
2 * CDDL HEADER START
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]
19 * CDDL HEADER END
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.
37 #include <stdlib.h>
38 #include <syslog.h>
39 #include <alloca.h>
40 #include <arpa/inet.h>
41 #include <resolv.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,
49 char *domain);
51 static void smb_netlogon_samlogon(struct name_entry *, char *,
52 char *, smb_sid_t *);
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);
61 * ntdomain_info
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
74 * format.
76 * Netlogon responses are received asynchronously and eventually handled
77 * in smb_netlogon_receive.
79 void
80 smb_netlogon_request(struct name_entry *server, char *domain)
82 smb_domain_t di;
83 smb_sid_t *sid = NULL;
84 int protocol = NETLOGON_PROTO_NETLOGON;
86 if (domain == NULL || *domain == '\0')
87 return;
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,
102 domain, sid);
103 else
104 smb_netlogon_query(server, MAILSLOT_NETLOGON_RDC, domain);
106 smb_sid_free(sid);
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.
118 void
119 smb_netlogon_receive(struct datagram *datagram,
120 char *mailbox,
121 unsigned char *data,
122 int datalen)
124 struct netlogon_opt {
125 char *mailslot;
126 void (*handler)();
127 } netlogon_opt[] = {
128 { MAILSLOT_NETLOGON_RDC, smb_netlogon_rdc_rsp },
129 { MAILSLOT_NETLOGON_SAMLOGON_RDC, smb_netlogon_rdc_rsp },
132 smb_msgbuf_t mb;
133 unsigned short opcode;
134 char src_name[SMB_PI_MAX_HOST];
135 smb_wchar_t unicode_src_name[SMB_PI_MAX_HOST];
136 uint32_t src_ipaddr;
137 char *junk;
138 char *primary;
139 char *domain;
140 int i;
141 char ipstr[16];
142 int rc;
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,
158 sizeof (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);
167 return;
170 switch (opcode) {
171 case LOGON_PRIMARY_RESPONSE:
173 * Message contains:
174 * PDC name (MBS), PDC name (Unicode), Domain name (unicode)
176 rc = smb_msgbuf_decode(&mb, "sUU", &junk, &primary, &domain);
177 if (rc < 0) {
178 syslog(LOG_ERR,
179 "NetLogonResponse: opcode %d decode error",
180 opcode);
181 smb_msgbuf_term(&mb);
182 return;
184 break;
186 case LOGON_SAM_LOGON_RESPONSE:
187 case LOGON_SAM_USER_UNKNOWN:
189 * Message contains:
190 * PDC name, User name, Domain name (all unicode)
192 rc = smb_msgbuf_decode(&mb, "UUU", &primary, &junk, &domain);
193 if (rc < 0) {
194 syslog(LOG_ERR,
195 "NetLogonResponse: opcode %d decode error",
196 opcode);
197 smb_msgbuf_term(&mb);
198 return;
202 * skip past the "\\" prefix
204 primary += strspn(primary, "\\");
205 break;
207 default:
209 * We don't respond to PDC discovery requests.
211 syslog(LOG_DEBUG, "NetLogonReceive: opcode 0x%04x", opcode);
212 smb_msgbuf_term(&mb);
213 return;
216 if (domain == NULL || primary == NULL) {
217 syslog(LOG_ERR, "NetLogonResponse: malformed packet");
218 smb_msgbuf_term(&mb);
219 return;
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);
231 return;
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);
240 return;
244 syslog(LOG_DEBUG, "NetLogonReceive[%s]: unknown mailslot", mailbox);
245 smb_msgbuf_term(&mb);
251 * smb_netlogon_query
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
267 * };
269 static void
270 smb_netlogon_query(struct name_entry *server,
271 char *mailbox,
272 char *domain)
274 smb_msgbuf_t mb;
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)
280 return;
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) +
291 sizeof (short);
293 offset = smb_browser_load_transact_header(buffer,
294 sizeof (buffer), data_length, ONE_WAY_TRANSACTION,
295 MAILSLOT_NETLOGON);
297 if (offset < 0)
298 return;
300 smb_msgbuf_init(&mb, buffer + offset, sizeof (buffer) - offset, 0);
302 announce_len = smb_msgbuf_encode(&mb, "wssUlww",
303 (short)LOGON_PRIMARY_QUERY,
304 hostname,
305 mailbox,
306 hostname,
307 0x1,
308 0xffff,
309 0xffff);
311 if (announce_len <= 0) {
312 smb_msgbuf_term(&mb);
313 syslog(LOG_ERR, "NetLogonQuery: encode error");
314 return;
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
344 * };
346 static void
347 smb_netlogon_samlogon(struct name_entry *server,
348 char *mailbox,
349 char *domain,
350 smb_sid_t *domain_sid)
352 smb_msgbuf_t mb;
353 unsigned domain_sid_len;
354 char *username;
355 unsigned char buffer[MAX_DATAGRAM_LENGTH];
356 int offset;
357 int announce_len;
358 int data_length;
359 int name_length;
360 char hostname[NETBIOS_NAME_SZ];
362 syslog(LOG_DEBUG, "NetLogonSamLogonReq: %s", domain);
364 if (smb_getnetbiosname(hostname, sizeof (hostname)) != 0)
365 return;
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)
382 + sizeof (short)
383 + smb_wcequiv_strlen(hostname) + 2
384 + smb_wcequiv_strlen(username) + 2
385 + name_length
386 + sizeof (long)
387 + sizeof (long)
388 + domain_sid_len + 3 /* padding */
389 + sizeof (long)
390 + sizeof (short)
391 + sizeof (short);
393 offset = smb_browser_load_transact_header(buffer,
394 sizeof (buffer), data_length, ONE_WAY_TRANSACTION,
395 MAILSLOT_NTLOGON);
397 if (offset < 0) {
398 syslog(LOG_ERR, "NetLogonSamLogonReq: header error");
399 return;
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);
422 return;
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.
433 static void
434 smb_netlogon_send(struct name_entry *name,
435 char *domain,
436 unsigned char *buffer,
437 int count)
439 static char suffix[] = { 0x1B, 0x1C };
440 struct name_entry dname;
441 struct name_entry *dest;
442 struct name_entry *dest_dup;
443 int i;
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);
454 if (dest_dup) {
455 (void) smb_netbios_datagram_send(name,
456 dest_dup, buffer, count);
457 free(dest_dup);
459 } else {
460 syslog(LOG_DEBUG,
461 "SmbNetlogonSend: could not find %s<0x%X>",
462 domain, suffix[i]);
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.
473 static void
474 smb_netlogon_rdc_rsp(char *src_name, uint32_t src_ipaddr)
476 static int initialized = 0;
477 uint32_t ipaddr;
478 uint32_t prefer_ipaddr;
479 char ipstr[INET_ADDRSTRLEN];
480 char srcip[INET_ADDRSTRLEN];
481 int rc;
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);
488 if (rc == 0)
489 prefer_ipaddr = 0;
491 if (!initialized) {
492 syslog(LOG_DEBUG, "SMB DC Preference: %s", ipstr);
493 initialized = 1;
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);
507 return;
510 ipaddr = ntdomain_info.n_ipaddr;
511 } else
512 ipaddr = 0;
514 if (smb_better_dc(ipaddr, src_ipaddr) ||
515 (prefer_ipaddr != 0 && prefer_ipaddr == src_ipaddr)) {
516 /* set nbtd cache */
517 (void) strlcpy(ntdomain_info.n_name, src_name,
518 SMB_PI_MAX_DOMAIN);
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);
527 static int
528 smb_better_dc(uint32_t cur_ip, uint32_t new_ip)
530 smb_inaddr_t ipaddr;
533 * If we don't have any current DC,
534 * then use the new one of course.
537 if (cur_ip == 0)
538 return (1);
540 * see if there is a DC in the
541 * same subnet
544 ipaddr.a_family = AF_INET;
545 ipaddr.a_ipv4 = cur_ip;
546 if (smb_nic_is_same_subnet(&ipaddr))
547 return (0);
549 ipaddr.a_family = AF_INET;
550 ipaddr.a_ipv4 = new_ip;
551 if (smb_nic_is_same_subnet(&ipaddr))
552 return (1);
554 * Otherwise, just keep the old one.
556 return (0);