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