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