xref: /illumos-gate/usr/src/lib/smbsrv/libsmbns/common/smbns_netlogon.c (revision dc20a3024900c47dd2ee44b9707e6df38f7d62a5)
1 /*
2  * CDDL HEADER START
3  *
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.
7  *
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.
12  *
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]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * This module handles the primary domain controller location protocol.
30  * The document claims to be version 1.15 of the browsing protocol. It also
31  * claims to specify the mailslot protocol.
32  *
33  * The NETLOGON protocol uses \MAILSLOT\NET mailslots. The protocol
34  * specification is incomplete, contains errors and is out-of-date but
35  * it does provide some useful background information. The document
36  * doesn't mention the NETLOGON_SAMLOGON version of the protocol.
37  */
38 
39 #include <stdlib.h>
40 #include <syslog.h>
41 #include <alloca.h>
42 #include <arpa/inet.h>
43 #include <resolv.h>
44 
45 #include <smbsrv/mailslot.h>
46 #include <smbsrv/libsmbns.h>
47 #include <smbns_ads.h>
48 #include <smbns_browser.h>
49 #include <smbns_netbios.h>
50 
51 static void smb_netlogon_query(struct name_entry *server, char *mailbox,
52     char *domain);
53 
54 static void smb_netlogon_samlogon(struct name_entry *server, char *mailbox,
55     char *domain);
56 
57 static void smb_netlogon_send(struct name_entry *name, char *domain,
58     unsigned char *buffer, int count);
59 
60 static void smb_netlogon_rdc_rsp(char *src_name, uint32_t src_ipaddr);
61 static int better_dc(uint32_t cur_ip, uint32_t new_ip);
62 
63 static char resource_domain[SMB_PI_MAX_DOMAIN];
64 
65 /*
66  * smb_netlogon_request
67  *
68  * This is the entry point locating the resource domain PDC. A netlogon
69  * request is sent using the specified protocol on the specified network.
70  * Note that we need to know the domain SID in order to use the samlogon
71  * format.
72  *
73  * Netlogon responses are received asynchronously and eventually handled
74  * in smb_netlogon_receive.
75  */
76 void
77 smb_netlogon_request(int net, int protocol, char *domain)
78 {
79 	struct name_entry *server;
80 	nt_domain_t *ntdp;
81 
82 	server = smb_browser_get_srvname(net);
83 	if (server == 0)
84 		return;
85 
86 	(void) strlcpy(resource_domain, domain,
87 	    sizeof (resource_domain));
88 
89 	if (strlen(resource_domain) > 0) {
90 		ntdp = nt_domain_lookup_name(resource_domain);
91 		if (protocol == NETLOGON_PROTO_SAMLOGON && ntdp)
92 			smb_netlogon_samlogon(server,
93 			    MAILSLOT_NETLOGON_SAMLOGON_RDC,
94 			    resource_domain);
95 		else
96 			smb_netlogon_query(server,
97 			    MAILSLOT_NETLOGON_RDC,
98 			    resource_domain);
99 	}
100 }
101 
102 /*
103  * smb_netlogon_receive
104  *
105  * This is where we handle all incoming NetLogon messages. Currently, we
106  * ignore requests from anyone else. We are only interested in responses
107  * to our own requests. The NetLogonResponse provides the name of the PDC.
108  * If we don't already have a controller name, we use the name provided
109  * in the message. Otherwise we use the name already in the environment.
110  */
111 void
112 smb_netlogon_receive(struct datagram *datagram,
113 				char *mailbox,
114 				unsigned char *data,
115 				int datalen)
116 {
117 	struct netlogon_opt {
118 		char *mailslot;
119 		void (*handler)();
120 	} netlogon_opt[] = {
121 		{ MAILSLOT_NETLOGON_RDC, smb_netlogon_rdc_rsp },
122 		{ MAILSLOT_NETLOGON_SAMLOGON_RDC, smb_netlogon_rdc_rsp },
123 	};
124 
125 	smb_msgbuf_t mb;
126 	unsigned short opcode;
127 	char src_name[SMB_PI_MAX_HOST];
128 	mts_wchar_t unicode_src_name[SMB_PI_MAX_HOST];
129 	unsigned int cpid = oem_get_smb_cpid();
130 	uint32_t src_ipaddr;
131 	char *junk;
132 	char *primary;
133 	char *domain;
134 	int i;
135 	char ipstr[16];
136 	int rc;
137 
138 	src_ipaddr = datagram->src.addr_list.sin.sin_addr.s_addr;
139 
140 	/*
141 	 * The datagram->src.name is in oem codepage format.
142 	 * Therefore, we need to convert it to unicode and
143 	 * store it in multi-bytes format.
144 	 */
145 	(void) oemstounicodes(unicode_src_name, (char *)datagram->src.name,
146 	    SMB_PI_MAX_HOST, cpid);
147 	(void) mts_wcstombs(src_name, unicode_src_name, SMB_PI_MAX_HOST);
148 
149 	(void) trim_whitespace(src_name);
150 
151 	(void) inet_ntop(AF_INET, (const void *)(&src_ipaddr), ipstr,
152 	    sizeof (ipstr));
153 	syslog(LOG_DEBUG, "NetLogonReceive: src=%s [%s], mbx=%s",
154 	    src_name, ipstr, mailbox);
155 
156 	smb_msgbuf_init(&mb, data, datalen, 0);
157 
158 	if (smb_msgbuf_decode(&mb, "w", &opcode) < 0) {
159 		syslog(LOG_ERR, "NetLogonReceive: decode error");
160 		smb_msgbuf_term(&mb);
161 		return;
162 	}
163 
164 	switch (opcode) {
165 	case LOGON_PRIMARY_RESPONSE:
166 		/*
167 		 * Message contains:
168 		 * PDC name (MBS), PDC name (Unicode), Domain name (unicode)
169 		 */
170 		rc = smb_msgbuf_decode(&mb, "sUU", &junk, &primary, &domain);
171 		if (rc < 0) {
172 			syslog(LOG_ERR,
173 			    "NetLogonResponse: opcode %d decode error",
174 			    opcode);
175 			smb_msgbuf_term(&mb);
176 			return;
177 		}
178 		break;
179 
180 	case LOGON_SAM_LOGON_RESPONSE:
181 	case LOGON_SAM_USER_UNKNOWN:
182 		/*
183 		 * Message contains:
184 		 * PDC name, User name, Domain name (all unicode)
185 		 */
186 		rc = smb_msgbuf_decode(&mb, "UUU", &primary, &junk, &domain);
187 		if (rc < 0) {
188 			syslog(LOG_ERR,
189 			    "NetLogonResponse: opcode %d decode error",
190 			    opcode);
191 			smb_msgbuf_term(&mb);
192 			return;
193 		}
194 
195 		/*
196 		 * skip past the "\\" prefix
197 		 */
198 		primary += strspn(primary, "\\");
199 		break;
200 
201 	default:
202 		/*
203 		 * We don't respond to PDC discovery requests.
204 		 */
205 		syslog(LOG_DEBUG, "NetLogonReceive: opcode 0x%04x", opcode);
206 		smb_msgbuf_term(&mb);
207 		return;
208 	}
209 
210 	if (domain == 0 || primary == 0) {
211 		syslog(LOG_ERR, "NetLogonResponse: malformed packet");
212 		smb_msgbuf_term(&mb);
213 		return;
214 	}
215 
216 	syslog(LOG_DEBUG, "DC Offer Dom=%s PDC=%s From=%s",
217 	    domain, primary, src_name);
218 
219 	if (strcasecmp(domain, resource_domain)) {
220 		syslog(LOG_DEBUG, "NetLogonResponse: other domain "
221 		    "%s, requested %s", domain, resource_domain);
222 		smb_msgbuf_term(&mb);
223 		return;
224 	}
225 
226 	for (i = 0; i < sizeof (netlogon_opt)/sizeof (netlogon_opt[0]); ++i) {
227 		if (strcasecmp(netlogon_opt[i].mailslot, mailbox) == 0) {
228 			syslog(LOG_DEBUG, "NetLogonReceive: %s", mailbox);
229 			(*netlogon_opt[i].handler)(primary, src_ipaddr);
230 			smb_msgbuf_term(&mb);
231 			return;
232 		}
233 	}
234 
235 	syslog(LOG_DEBUG, "NetLogonReceive[%s]: unknown mailslot", mailbox);
236 	smb_msgbuf_term(&mb);
237 }
238 
239 
240 
241 /*
242  * smb_netlogon_query
243  *
244  * Build and send a LOGON_PRIMARY_QUERY to the MAILSLOT_NETLOGON. At some
245  * point we should receive a LOGON_PRIMARY_RESPONSE in the mailslot we
246  * specify in the request.
247  *
248  *  struct NETLOGON_QUERY {
249  *	unsigned short Opcode;		# LOGON_PRIMARY_QUERY
250  *	char ComputerName[];		# ASCII hostname. The response
251  *					# is sent to <ComputerName>(00).
252  *	char MailslotName[];		# MAILSLOT_NETLOGON
253  *	char Pad[];			# Pad to short
254  *	wchar_t ComputerName[]		# UNICODE hostname
255  *	DWORD NT_Version;		# 0x00000001
256  *	WORD LmNTToken;			# 0xffff
257  *	WORD Lm20Token;			# 0xffff
258  *  };
259  */
260 static void
261 smb_netlogon_query(struct name_entry *server,
262 			char *mailbox,
263 			char *domain)
264 {
265 	smb_msgbuf_t mb;
266 	int offset, announce_len, data_length, name_lengths;
267 	unsigned char buffer[MAX_DATAGRAM_LENGTH];
268 	char hostname[MAXHOSTNAMELEN];
269 
270 	if (smb_gethostname(hostname, MAXHOSTNAMELEN, 1) != 0)
271 		return;
272 
273 	name_lengths = strlen(mailbox)+1+strlen(hostname)+1;
274 
275 	/*
276 	 * The (name_lengths & 1) part is to word align the name_lengths
277 	 * before the wc equiv strlen and the "+ 2" is to cover the two
278 	 * zero bytes that terminate the wchar string.
279 	 */
280 	data_length = sizeof (short) + name_lengths + (name_lengths & 1) +
281 	    mts_wcequiv_strlen(hostname) + 2 + sizeof (long) + sizeof (short) +
282 	    sizeof (short);
283 
284 	offset = smb_browser_load_transact_header(buffer,
285 	    sizeof (buffer), data_length, ONE_WAY_TRANSACTION,
286 	    MAILSLOT_NETLOGON);
287 
288 	if (offset < 0)
289 		return;
290 
291 	smb_msgbuf_init(&mb, buffer + offset, sizeof (buffer) - offset, 0);
292 
293 	announce_len = smb_msgbuf_encode(&mb, "wssUlww",
294 	    (short)LOGON_PRIMARY_QUERY,
295 	    hostname,
296 	    mailbox,
297 	    hostname,
298 	    0x1,
299 	    0xffff,
300 	    0xffff);
301 
302 	if (announce_len <= 0) {
303 		smb_msgbuf_term(&mb);
304 		syslog(LOG_ERR, "NetLogonQuery: encode error");
305 		return;
306 	}
307 
308 	smb_netlogon_send(server, domain, buffer, offset + announce_len);
309 	smb_msgbuf_term(&mb);
310 }
311 
312 
313 /*
314  * smb_netlogon_samlogon
315  *
316  * The SamLogon version of the NetLogon request uses the workstation trust
317  * account and, I think, may be a prerequisite to the challenge/response
318  * netr authentication. The trust account username is the hostname with a
319  * $ appended. The mailslot for this request is MAILSLOT_NTLOGON. At some
320  * we should receive a LOGON_SAM_LOGON_RESPONSE in the mailslot we
321  * specify in the request.
322  *
323  * struct NETLOGON_SAM_LOGON {
324  *	unsigned short Opcode;			# LOGON_SAM_LOGON_REQUEST
325  *	unsigned short RequestCount;		# 0
326  *	wchar_t UnicodeComputerName;		# hostname
327  *	wchar_t UnicodeUserName;		# hostname$
328  *	char *MailslotName;			# response mailslot
329  *	DWORD AllowableAccountControlBits;	# 0x80 = WorkstationTrustAccount
330  *	DWORD DomainSidSize;			# domain sid length in bytes
331  *	BYTE *DomainSid;			# domain sid
332  *	uint32_t   NT_Version;		# 0x00000001
333  *	unsigned short  LmNTToken;		# 0xffff
334  *	unsigned short  Lm20Token;		# 0xffff
335  * };
336  */
337 static void
338 smb_netlogon_samlogon(struct name_entry *server,
339 			char *mailbox,
340 			char *domain)
341 {
342 	smb_msgbuf_t mb;
343 	nt_domain_t *ntdp;
344 	nt_sid_t *domain_sid;
345 	unsigned domain_sid_len;
346 	char *username;
347 	unsigned char buffer[MAX_DATAGRAM_LENGTH];
348 	int offset;
349 	int announce_len;
350 	int data_length;
351 	int name_length;
352 	char hostname[MAXHOSTNAMELEN];
353 
354 	syslog(LOG_DEBUG, "NetLogonSamLogonReq: %s", domain);
355 
356 	if ((ntdp = nt_domain_lookup_name(domain)) == 0) {
357 		syslog(LOG_ERR, "NetLogonSamLogonReq[%s]: no sid", domain);
358 		return;
359 	}
360 
361 	domain_sid = ntdp->sid;
362 	domain_sid_len = nt_sid_length(domain_sid);
363 	nt_sid_logf(domain_sid);
364 
365 	if (smb_gethostname(hostname, MAXHOSTNAMELEN, 1) != 0)
366 		return;
367 
368 	/*
369 	 * The username will be the trust account name on the PDC.
370 	 */
371 	name_length = strlen(hostname) + 2;
372 	username = alloca(name_length);
373 	(void) snprintf(username, name_length, "%s$", hostname);
374 
375 	/*
376 	 * Add 2 to wide-char equivalent strlen to cover the
377 	 * two zero bytes that terminate the wchar string.
378 	 */
379 	name_length = strlen(mailbox)+1;
380 
381 	data_length = sizeof (short)
382 	    + sizeof (short)
383 	    + mts_wcequiv_strlen(hostname) + 2
384 	    + mts_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);
392 
393 	offset = smb_browser_load_transact_header(buffer,
394 	    sizeof (buffer), data_length, ONE_WAY_TRANSACTION,
395 	    MAILSLOT_NTLOGON);
396 
397 	if (offset < 0) {
398 		syslog(LOG_ERR, "NetLogonSamLogonReq: header error");
399 		return;
400 	}
401 
402 	/*
403 	 * The domain SID is padded with 3 leading zeros.
404 	 */
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 */
418 
419 	if (announce_len <= 0) {
420 		syslog(LOG_ERR, "NetLogonSamLogonReq: encode error");
421 		smb_msgbuf_term(&mb);
422 		return;
423 	}
424 
425 	smb_netlogon_send(server, domain, buffer, offset + announce_len);
426 	smb_msgbuf_term(&mb);
427 }
428 
429 
430 /*
431  * Send a query for each version of the protocol.
432  */
433 static void
434 smb_netlogon_send(struct name_entry *name,
435 			char *domain,
436 			unsigned char *buffer,
437 			int count)
438 {
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;
444 
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);
448 
449 		syslog(LOG_DEBUG, "smb_netlogon_send");
450 		smb_netbios_name_dump(&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, dest_dup,
456 				    buffer, count);
457 				free(dest_dup);
458 			}
459 		} else {
460 			syslog(LOG_DEBUG, "smbd: NBNS couldn't find %s<0x%X>",
461 			    domain, suffix[i]);
462 		}
463 	}
464 }
465 
466 /*
467  * smb_netlogon_rdc_rsp
468  *
469  * This is where we process netlogon responses for the resource domain.
470  * The src_name is the real name of the remote machine.
471  */
472 static void
473 smb_netlogon_rdc_rsp(char *src_name, uint32_t src_ipaddr)
474 {
475 	static int initialized = 0;
476 	smb_ntdomain_t *pi;
477 	uint32_t ipaddr;
478 	uint32_t prefer_ipaddr = 0;
479 	char ipstr[16];
480 	char srcip[16];
481 	int rc;
482 
483 	(void) inet_ntop(AF_INET, (const void *)(&src_ipaddr),
484 	    srcip, sizeof (srcip));
485 
486 	rc = smb_config_getstr(SMB_CI_DOMAIN_SRV, ipstr, sizeof (ipstr));
487 	if (rc == SMBD_SMF_OK) {
488 		rc = inet_pton(AF_INET, ipstr, &prefer_ipaddr);
489 		if (rc == 0)
490 			prefer_ipaddr = 0;
491 
492 		if (!initialized) {
493 			syslog(LOG_DEBUG, "SMB DC Preference: %s", ipstr);
494 			initialized = 1;
495 		}
496 	}
497 
498 	syslog(LOG_DEBUG, "DC Offer [%s]: %s [%s]",
499 	    resource_domain, src_name, srcip);
500 
501 	if ((pi = smb_getdomaininfo(0)) != 0) {
502 		if (prefer_ipaddr != 0 && prefer_ipaddr == pi->ipaddr) {
503 			syslog(LOG_DEBUG, "DC for %s: %s [%s]",
504 			    resource_domain, src_name, srcip);
505 			return;
506 		}
507 
508 		ipaddr = pi->ipaddr;
509 	} else
510 		ipaddr = 0;
511 
512 	if (better_dc(ipaddr, src_ipaddr) ||
513 	    (prefer_ipaddr != 0 && prefer_ipaddr == src_ipaddr)) {
514 		smb_setdomaininfo(resource_domain, src_name,
515 		    src_ipaddr);
516 		syslog(LOG_DEBUG, "DC discovered for %s: %s [%s]",
517 		    resource_domain, src_name, srcip);
518 	}
519 }
520 
521 static int
522 better_dc(uint32_t cur_ip, uint32_t new_ip)
523 {
524 	net_cfg_t cfg;
525 
526 	/*
527 	 * If we don't have any current DC,
528 	 * then use the new one of course.
529 	 */
530 	if (cur_ip == 0)
531 		return (1);
532 
533 	if (smb_nic_get_bysubnet(cur_ip, &cfg) != NULL)
534 		return (0);
535 	if (smb_nic_get_bysubnet(new_ip, &cfg) != NULL)
536 		return (1);
537 	/*
538 	 * Otherwise, just keep the old one.
539 	 */
540 	return (0);
541 }
542 
543 /*
544  * msdcs_lookup_ads
545  *
546  * Try to find a domain controller in ADS. Actually we want to query DNS
547  * but we need to find out if ADS is enabled and this is probably the
548  * best way. The IP address isn't set up in the ADS_HANDLE so we need to
549  * make the ads_find_host call. This will only succeed if ADS is enabled.
550  *
551  * Parameter:
552  *    nbt_domain - NETBIOS name of the domain
553  *
554  * Returns 1 if a domain controller was found and its name and IP address
555  * have been updated. Otherwise returns 0.
556  */
557 int
558 msdcs_lookup_ads(char *nbt_domain)
559 {
560 	ADS_HOST_INFO *hinfo = 0;
561 	int ads_port = 0;
562 	char ads_domain[MAXHOSTNAMELEN];
563 	char site_service[MAXHOSTNAMELEN];
564 	char service[MAXHOSTNAMELEN];
565 	char site[MAXHOSTNAMELEN];
566 	char *p;
567 	char *ip_addr;
568 	struct in_addr ns_list[MAXNS];
569 	int i, cnt, go_next;
570 
571 	if (!nbt_domain)
572 		return (0);
573 
574 	(void) strlcpy(resource_domain, nbt_domain, SMB_PI_MAX_DOMAIN);
575 	if (smb_resolve_fqdn(nbt_domain, ads_domain, MAXHOSTNAMELEN) != 1)
576 		return (0);
577 
578 	(void) smb_config_getstr(SMB_CI_ADS_SITE, site, sizeof (site));
579 	if (*site != '\0') {
580 		(void) snprintf(site_service, MAXHOSTNAMELEN,
581 		    "_ldap._tcp.%s._sites.dc._msdcs.%s",
582 		    site, ads_domain);
583 	}
584 
585 	(void) snprintf(service, MAXHOSTNAMELEN,
586 	    "_ldap._tcp.dc._msdcs.%s", ads_domain);
587 
588 	cnt = smb_get_nameservers(ns_list, MAXNS);
589 
590 	go_next = 0;
591 	for (i = 0; i < cnt; i++) {
592 		ip_addr = inet_ntoa(ns_list[i]);
593 
594 		hinfo = ads_find_host(ip_addr, ads_domain, &ads_port,
595 		    site_service, &go_next);
596 
597 		if (hinfo == NULL) {
598 			hinfo = ads_find_host(ip_addr, ads_domain, &ads_port,
599 			    service, &go_next);
600 		}
601 
602 		if ((hinfo != NULL) || (go_next == 0))
603 			break;
604 	}
605 
606 	if (hinfo == NULL) {
607 		syslog(LOG_DEBUG, "msdcsLookupADS: unable to find host");
608 		return (0);
609 	}
610 
611 	syslog(LOG_DEBUG, "msdcsLookupADS: %s [%I]", hinfo->name,
612 	    hinfo->ip_addr);
613 
614 	/*
615 	 * Remove the domain extension - the
616 	 * NetBIOS browser can't handle it.
617 	 */
618 	if ((p = strchr(hinfo->name, '.')) != 0)
619 		*p = '\0';
620 
621 	smb_netlogon_rdc_rsp(hinfo->name, hinfo->ip_addr);
622 
623 	return (1);
624 }
625