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