xref: /illumos-gate/usr/src/lib/smbsrv/libsmbns/common/smbns_netlogon.c (revision 628e3cbed6489fa1db545d8524a06cd6535af456)
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	"@(#)smbns_netlogon.c	1.7	08/07/16 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 smb_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 	smb_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 = smb_sid_len(domain_sid);
358 
359 	if (smb_gethostname(hostname, MAXHOSTNAMELEN, 1) != 0)
360 		return;
361 
362 	/*
363 	 * The username will be the trust account name on the PDC.
364 	 */
365 	name_length = strlen(hostname) + 2;
366 	username = alloca(name_length);
367 	(void) snprintf(username, name_length, "%s$", hostname);
368 
369 	/*
370 	 * Add 2 to wide-char equivalent strlen to cover the
371 	 * two zero bytes that terminate the wchar string.
372 	 */
373 	name_length = strlen(mailbox)+1;
374 
375 	data_length = sizeof (short)
376 	    + sizeof (short)
377 	    + mts_wcequiv_strlen(hostname) + 2
378 	    + mts_wcequiv_strlen(username) + 2
379 	    + name_length
380 	    + sizeof (long)
381 	    + sizeof (long)
382 	    + domain_sid_len + 3 /* padding */
383 	    + sizeof (long)
384 	    + sizeof (short)
385 	    + sizeof (short);
386 
387 	offset = smb_browser_load_transact_header(buffer,
388 	    sizeof (buffer), data_length, ONE_WAY_TRANSACTION,
389 	    MAILSLOT_NTLOGON);
390 
391 	if (offset < 0) {
392 		syslog(LOG_ERR, "NetLogonSamLogonReq: header error");
393 		return;
394 	}
395 
396 	/*
397 	 * The domain SID is padded with 3 leading zeros.
398 	 */
399 	smb_msgbuf_init(&mb, buffer + offset, sizeof (buffer) - offset, 0);
400 	announce_len = smb_msgbuf_encode(&mb, "wwUUsll3.#clww",
401 	    (short)LOGON_SAM_LOGON_REQUEST,
402 	    0,				/* RequestCount */
403 	    hostname,	/* UnicodeComputerName */
404 	    username,			/* UnicodeUserName */
405 	    mailbox,			/* MailslotName */
406 	    0x00000080,			/* AllowableAccountControlBits */
407 	    domain_sid_len,		/* DomainSidSize */
408 	    domain_sid_len, domain_sid,	/* DomainSid */
409 	    0x00000001,			/* NT_Version */
410 	    0xffff,			/* LmNTToken */
411 	    0xffff);			/* Lm20Token */
412 
413 	if (announce_len <= 0) {
414 		syslog(LOG_ERR, "NetLogonSamLogonReq: encode error");
415 		smb_msgbuf_term(&mb);
416 		return;
417 	}
418 
419 	smb_netlogon_send(server, domain, buffer, offset + announce_len);
420 	smb_msgbuf_term(&mb);
421 }
422 
423 
424 /*
425  * Send a query for each version of the protocol.
426  */
427 static void
428 smb_netlogon_send(struct name_entry *name,
429 			char *domain,
430 			unsigned char *buffer,
431 			int count)
432 {
433 	static char suffix[] = { 0x1B, 0x1C };
434 	struct name_entry dname;
435 	struct name_entry *dest;
436 	struct name_entry *dest_dup;
437 	int i;
438 
439 	for (i = 0; i < sizeof (suffix)/sizeof (suffix[0]); i++) {
440 		smb_init_name_struct((unsigned char *)domain, suffix[i],
441 		    0, 0, 0, 0, 0, &dname);
442 
443 		syslog(LOG_DEBUG, "smb_netlogon_send");
444 		smb_netbios_name_dump(&dname);
445 		if ((dest = smb_name_find_name(&dname)) != 0) {
446 			dest_dup = smb_netbios_name_dup(dest, 1);
447 			smb_name_unlock_name(dest);
448 			if (dest_dup) {
449 				(void) smb_netbios_datagram_send(name, dest_dup,
450 				    buffer, count);
451 				free(dest_dup);
452 			}
453 		} else {
454 			syslog(LOG_DEBUG, "smbd: NBNS couldn't find %s<0x%X>",
455 			    domain, suffix[i]);
456 		}
457 	}
458 }
459 
460 /*
461  * smb_netlogon_rdc_rsp
462  *
463  * This is where we process netlogon responses for the resource domain.
464  * The src_name is the real name of the remote machine.
465  */
466 static void
467 smb_netlogon_rdc_rsp(char *src_name, uint32_t src_ipaddr)
468 {
469 	static int initialized = 0;
470 	smb_ntdomain_t *pi;
471 	uint32_t ipaddr;
472 	uint32_t prefer_ipaddr = 0;
473 	char ipstr[16];
474 	char srcip[16];
475 	int rc;
476 
477 	(void) inet_ntop(AF_INET, (const void *)(&src_ipaddr),
478 	    srcip, sizeof (srcip));
479 
480 	rc = smb_config_getstr(SMB_CI_DOMAIN_SRV, ipstr, sizeof (ipstr));
481 	if (rc == SMBD_SMF_OK) {
482 		rc = inet_pton(AF_INET, ipstr, &prefer_ipaddr);
483 		if (rc == 0)
484 			prefer_ipaddr = 0;
485 
486 		if (!initialized) {
487 			syslog(LOG_DEBUG, "SMB DC Preference: %s", ipstr);
488 			initialized = 1;
489 		}
490 	}
491 
492 	syslog(LOG_DEBUG, "DC Offer [%s]: %s [%s]",
493 	    resource_domain, src_name, srcip);
494 
495 	if ((pi = smb_getdomaininfo(0)) != 0) {
496 		if (prefer_ipaddr != 0 && prefer_ipaddr == pi->ipaddr) {
497 			syslog(LOG_DEBUG, "DC for %s: %s [%s]",
498 			    resource_domain, src_name, srcip);
499 			return;
500 		}
501 
502 		ipaddr = pi->ipaddr;
503 	} else
504 		ipaddr = 0;
505 
506 	if (smb_better_dc(ipaddr, src_ipaddr) ||
507 	    (prefer_ipaddr != 0 && prefer_ipaddr == src_ipaddr)) {
508 		smb_setdomaininfo(resource_domain, src_name,
509 		    src_ipaddr);
510 		syslog(LOG_DEBUG, "DC discovered for %s: %s [%s]",
511 		    resource_domain, src_name, srcip);
512 	}
513 }
514 
515 static int
516 smb_better_dc(uint32_t cur_ip, uint32_t new_ip)
517 {
518 	/*
519 	 * If we don't have any current DC,
520 	 * then use the new one of course.
521 	 */
522 	if (cur_ip == 0)
523 		return (1);
524 
525 	if (smb_nic_exists(cur_ip, B_TRUE))
526 		return (0);
527 
528 	if (smb_nic_exists(new_ip, B_TRUE))
529 		return (1);
530 	/*
531 	 * Otherwise, just keep the old one.
532 	 */
533 	return (0);
534 }
535 
536 /*
537  * smb_msdcs_lookup_ads
538  *
539  * Try to find a domain controller in ADS.
540  *
541  * Parameter:
542  *    nbt_domain - NETBIOS name of the domain
543  *    server - the ADS server to be sought.
544  *
545  * Returns 1 if a domain controller was found and its name and IP address
546  * have been updated. Otherwise returns 0.
547  */
548 int
549 smb_msdcs_lookup_ads(char *nbt_domain, char *server)
550 {
551 	smb_ads_host_info_t *hinfo = NULL;
552 	int ads_port = 0;
553 	char ads_domain[MAXHOSTNAMELEN];
554 	char *p;
555 	char *nbt_hostname;
556 	struct in_addr addr;
557 
558 	if (!nbt_domain)
559 		return (0);
560 
561 	(void) strlcpy(resource_domain, nbt_domain, SMB_PI_MAX_DOMAIN);
562 	if (smb_resolve_fqdn(nbt_domain, ads_domain, MAXHOSTNAMELEN) != 1)
563 		return (0);
564 
565 	if ((hinfo = smb_ads_find_host(ads_domain, server, &ads_port))
566 	    == NULL) {
567 		syslog(LOG_DEBUG, "msdcsLookupADS: unable to find host");
568 		return (0);
569 	}
570 
571 	addr.s_addr = hinfo->ip_addr;
572 	syslog(LOG_DEBUG, "msdcsLookupADS: %s [%s]", hinfo->name,
573 	    inet_ntoa(addr));
574 
575 	/*
576 	 * Remove the domain extension - the
577 	 * NetBIOS browser can't handle it.
578 	 */
579 	nbt_hostname = strdup(hinfo->name);
580 	if ((p = strchr(nbt_hostname, '.')) != 0)
581 		*p = '\0';
582 
583 	smb_netlogon_rdc_rsp(nbt_hostname, hinfo->ip_addr);
584 	free(nbt_hostname);
585 
586 	return (1);
587 }
588