Home | History | Annotate | Download | only in common
      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