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