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