2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A/*
2N/A * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A/*
2N/A * This module handles the primary domain controller location protocol.
2N/A * The document claims to be version 1.15 of the browsing protocol. It also
2N/A * claims to specify the mailslot protocol.
2N/A *
2N/A * The NETLOGON protocol uses \MAILSLOT\NET mailslots. The protocol
2N/A * specification is incomplete, contains errors and is out-of-date but
2N/A * it does provide some useful background information. The document
2N/A * doesn't mention the NETLOGON_SAMLOGON version of the protocol.
2N/A */
2N/A
2N/A#include <stdlib.h>
2N/A#include <syslog.h>
2N/A#include <alloca.h>
2N/A#include <arpa/inet.h>
2N/A#include <resolv.h>
2N/A
2N/A#include <smb/mailslot.h>
2N/A#include <smbsrv/libsmbns.h>
2N/A#include <smbns_browser.h>
2N/A#include <smbns_netbios.h>
2N/A
2N/Astatic void smb_netlogon_query(struct name_entry *server, char *mailbox,
2N/A char *domain);
2N/A
2N/Astatic void smb_netlogon_samlogon(struct name_entry *, char *,
2N/A char *, smb_sid_t *);
2N/A
2N/Astatic void smb_netlogon_send(struct name_entry *name, char *domain,
2N/A unsigned char *buffer, int count);
2N/A
2N/Astatic void smb_netlogon_rdc_rsp(char *src_name, uint32_t src_ipaddr);
2N/Astatic int smb_better_dc(uint32_t cur_ip, uint32_t new_ip);
2N/A
2N/A/*
2N/A * ntdomain_info
2N/A * Temporary. It should be removed once NBTD is integrated.
2N/A */
2N/Aextern smb_ntdomain_t ntdomain_info;
2N/Aextern mutex_t ntdomain_mtx;
2N/Aextern cond_t ntdomain_cv;
2N/A
2N/A/*
2N/A * smb_netlogon_request
2N/A *
2N/A * This is the entry point locating the resource domain PDC. A netlogon
2N/A * request is sent using the specified protocol on the specified network.
2N/A * Note that we need to know the domain SID in order to use the samlogon
2N/A * format.
2N/A *
2N/A * Netlogon responses are received asynchronously and eventually handled
2N/A * in smb_netlogon_receive.
2N/A */
2N/Avoid
2N/Asmb_netlogon_request(struct name_entry *server, char *domain)
2N/A{
2N/A smb_domain_t di;
2N/A smb_sid_t *sid = NULL;
2N/A int protocol = NETLOGON_PROTO_NETLOGON;
2N/A
2N/A if (domain == NULL || *domain == '\0')
2N/A return;
2N/A
2N/A (void) mutex_lock(&ntdomain_mtx);
2N/A (void) strlcpy(ntdomain_info.n_domain, domain,
2N/A sizeof (ntdomain_info.n_domain));
2N/A (void) mutex_unlock(&ntdomain_mtx);
2N/A
2N/A smb_config_getdomaininfo(di.di_nbname, NULL, di.di_sid, NULL, NULL);
2N/A if (smb_strcasecmp(di.di_nbname, domain, 0) == 0) {
2N/A if ((sid = smb_sid_fromstr(di.di_sid)) != NULL)
2N/A protocol = NETLOGON_PROTO_SAMLOGON;
2N/A }
2N/A
2N/A if (protocol == NETLOGON_PROTO_SAMLOGON)
2N/A smb_netlogon_samlogon(server, MAILSLOT_NETLOGON_SAMLOGON_RDC,
2N/A domain, sid);
2N/A else
2N/A smb_netlogon_query(server, MAILSLOT_NETLOGON_RDC, domain);
2N/A
2N/A smb_sid_free(sid);
2N/A}
2N/A
2N/A/*
2N/A * smb_netlogon_receive
2N/A *
2N/A * This is where we handle all incoming NetLogon messages. Currently, we
2N/A * ignore requests from anyone else. We are only interested in responses
2N/A * to our own requests. The NetLogonResponse provides the name of the PDC.
2N/A * If we don't already have a controller name, we use the name provided
2N/A * in the message. Otherwise we use the name already in the environment.
2N/A */
2N/Avoid
2N/Asmb_netlogon_receive(struct datagram *datagram,
2N/A char *mailbox,
2N/A unsigned char *data,
2N/A int datalen)
2N/A{
2N/A struct netlogon_opt {
2N/A char *mailslot;
2N/A void (*handler)();
2N/A } netlogon_opt[] = {
2N/A { MAILSLOT_NETLOGON_RDC, smb_netlogon_rdc_rsp },
2N/A { MAILSLOT_NETLOGON_SAMLOGON_RDC, smb_netlogon_rdc_rsp },
2N/A };
2N/A
2N/A smb_msgbuf_t mb;
2N/A unsigned short opcode;
2N/A char src_name[SMB_PI_MAX_HOST];
2N/A smb_wchar_t unicode_src_name[SMB_PI_MAX_HOST];
2N/A uint32_t src_ipaddr;
2N/A char *junk;
2N/A char *primary;
2N/A char *domain;
2N/A int i;
2N/A char ipstr[16];
2N/A int rc;
2N/A
2N/A src_ipaddr = datagram->src.addr_list.sin.sin_addr.s_addr;
2N/A
2N/A /*
2N/A * The datagram->src.name is in oem codepage format.
2N/A * Therefore, we need to convert it to unicode and
2N/A * store it in multi-bytes format.
2N/A */
2N/A (void) oemtoucs(unicode_src_name, (char *)datagram->src.name,
2N/A SMB_PI_MAX_HOST, OEM_CPG_850);
2N/A (void) smb_wcstombs(src_name, unicode_src_name, SMB_PI_MAX_HOST);
2N/A
2N/A (void) trim_whitespace(src_name);
2N/A
2N/A (void) inet_ntop(AF_INET, (const void *)(&src_ipaddr), ipstr,
2N/A sizeof (ipstr));
2N/A syslog(LOG_DEBUG, "NetLogonReceive: src=%s [%s], mbx=%s",
2N/A src_name, ipstr, mailbox);
2N/A
2N/A smb_msgbuf_init(&mb, data, datalen, 0);
2N/A
2N/A if (smb_msgbuf_decode(&mb, "w", &opcode) < 0) {
2N/A syslog(LOG_ERR, "NetLogonReceive: decode error");
2N/A smb_msgbuf_term(&mb);
2N/A return;
2N/A }
2N/A
2N/A switch (opcode) {
2N/A case LOGON_PRIMARY_RESPONSE:
2N/A /*
2N/A * Message contains:
2N/A * PDC name (MBS), PDC name (Unicode), Domain name (unicode)
2N/A */
2N/A rc = smb_msgbuf_decode(&mb, "sUU", &junk, &primary, &domain);
2N/A if (rc < 0) {
2N/A syslog(LOG_ERR,
2N/A "NetLogonResponse: opcode %d decode error",
2N/A opcode);
2N/A smb_msgbuf_term(&mb);
2N/A return;
2N/A }
2N/A break;
2N/A
2N/A case LOGON_SAM_LOGON_RESPONSE:
2N/A case LOGON_SAM_USER_UNKNOWN:
2N/A /*
2N/A * Message contains:
2N/A * PDC name, User name, Domain name (all unicode)
2N/A */
2N/A rc = smb_msgbuf_decode(&mb, "UUU", &primary, &junk, &domain);
2N/A if (rc < 0) {
2N/A syslog(LOG_ERR,
2N/A "NetLogonResponse: opcode %d decode error",
2N/A opcode);
2N/A smb_msgbuf_term(&mb);
2N/A return;
2N/A }
2N/A
2N/A /*
2N/A * skip past the "\\" prefix
2N/A */
2N/A primary += strspn(primary, "\\");
2N/A break;
2N/A
2N/A default:
2N/A /*
2N/A * We don't respond to PDC discovery requests.
2N/A */
2N/A syslog(LOG_DEBUG, "NetLogonReceive: opcode 0x%04x", opcode);
2N/A smb_msgbuf_term(&mb);
2N/A return;
2N/A }
2N/A
2N/A if (domain == NULL || primary == NULL) {
2N/A syslog(LOG_ERR, "NetLogonResponse: malformed packet");
2N/A smb_msgbuf_term(&mb);
2N/A return;
2N/A }
2N/A
2N/A syslog(LOG_DEBUG, "DC Offer Domain=%s PDC=%s From=%s",
2N/A domain, primary, src_name);
2N/A
2N/A (void) mutex_lock(&ntdomain_mtx);
2N/A if (strcasecmp(domain, ntdomain_info.n_domain)) {
2N/A syslog(LOG_DEBUG, "NetLogonResponse: other domain "
2N/A "%s, requested %s", domain, ntdomain_info.n_domain);
2N/A smb_msgbuf_term(&mb);
2N/A (void) mutex_unlock(&ntdomain_mtx);
2N/A return;
2N/A }
2N/A (void) mutex_unlock(&ntdomain_mtx);
2N/A
2N/A for (i = 0; i < sizeof (netlogon_opt)/sizeof (netlogon_opt[0]); ++i) {
2N/A if (strcasecmp(netlogon_opt[i].mailslot, mailbox) == 0) {
2N/A syslog(LOG_DEBUG, "NetLogonReceive: %s", mailbox);
2N/A (*netlogon_opt[i].handler)(primary, src_ipaddr);
2N/A smb_msgbuf_term(&mb);
2N/A return;
2N/A }
2N/A }
2N/A
2N/A syslog(LOG_DEBUG, "NetLogonReceive[%s]: unknown mailslot", mailbox);
2N/A smb_msgbuf_term(&mb);
2N/A}
2N/A
2N/A
2N/A
2N/A/*
2N/A * smb_netlogon_query
2N/A *
2N/A * Build and send a LOGON_PRIMARY_QUERY to the MAILSLOT_NETLOGON. At some
2N/A * point we should receive a LOGON_PRIMARY_RESPONSE in the mailslot we
2N/A * specify in the request.
2N/A *
2N/A * struct NETLOGON_QUERY {
2N/A * unsigned short Opcode; # LOGON_PRIMARY_QUERY
2N/A * char ComputerName[]; # ASCII hostname. The response
2N/A * # is sent to <ComputerName>(00).
2N/A * char MailslotName[]; # MAILSLOT_NETLOGON
2N/A * char Pad[]; # Pad to short
2N/A * wchar_t ComputerName[] # UNICODE hostname
2N/A * DWORD NT_Version; # 0x00000001
2N/A * WORD LmNTToken; # 0xffff
2N/A * WORD Lm20Token; # 0xffff
2N/A * };
2N/A */
2N/Astatic void
2N/Asmb_netlogon_query(struct name_entry *server,
2N/A char *mailbox,
2N/A char *domain)
2N/A{
2N/A smb_msgbuf_t mb;
2N/A int offset, announce_len, data_length, name_lengths;
2N/A unsigned char buffer[MAX_DATAGRAM_LENGTH];
2N/A char hostname[NETBIOS_NAME_SZ];
2N/A
2N/A if (smb_getnetbiosname(hostname, sizeof (hostname)) != 0)
2N/A return;
2N/A
2N/A name_lengths = strlen(mailbox)+1+strlen(hostname)+1;
2N/A
2N/A /*
2N/A * The (name_lengths & 1) part is to word align the name_lengths
2N/A * before the wc equiv strlen and the "+ 2" is to cover the two
2N/A * zero bytes that terminate the wchar string.
2N/A */
2N/A data_length = sizeof (short) + name_lengths + (name_lengths & 1) +
2N/A smb_wcequiv_strlen(hostname) + 2 + sizeof (long) + sizeof (short) +
2N/A sizeof (short);
2N/A
2N/A offset = smb_browser_load_transact_header(buffer,
2N/A sizeof (buffer), data_length, ONE_WAY_TRANSACTION,
2N/A MAILSLOT_NETLOGON);
2N/A
2N/A if (offset < 0)
2N/A return;
2N/A
2N/A smb_msgbuf_init(&mb, buffer + offset, sizeof (buffer) - offset, 0);
2N/A
2N/A announce_len = smb_msgbuf_encode(&mb, "wssUlww",
2N/A (short)LOGON_PRIMARY_QUERY,
2N/A hostname,
2N/A mailbox,
2N/A hostname,
2N/A 0x1,
2N/A 0xffff,
2N/A 0xffff);
2N/A
2N/A if (announce_len <= 0) {
2N/A smb_msgbuf_term(&mb);
2N/A syslog(LOG_ERR, "NetLogonQuery: encode error");
2N/A return;
2N/A }
2N/A
2N/A smb_netlogon_send(server, domain, buffer, offset + announce_len);
2N/A smb_msgbuf_term(&mb);
2N/A}
2N/A
2N/A
2N/A/*
2N/A * smb_netlogon_samlogon
2N/A *
2N/A * The SamLogon version of the NetLogon request uses the workstation trust
2N/A * account and, I think, may be a prerequisite to the challenge/response
2N/A * netr authentication. The trust account username is the hostname with a
2N/A * $ appended. The mailslot for this request is MAILSLOT_NTLOGON. At some
2N/A * we should receive a LOGON_SAM_LOGON_RESPONSE in the mailslot we
2N/A * specify in the request.
2N/A *
2N/A * struct NETLOGON_SAM_LOGON {
2N/A * unsigned short Opcode; # LOGON_SAM_LOGON_REQUEST
2N/A * unsigned short RequestCount; # 0
2N/A * wchar_t UnicodeComputerName; # hostname
2N/A * wchar_t UnicodeUserName; # hostname$
2N/A * char *MailslotName; # response mailslot
2N/A * DWORD AllowableAccountControlBits; # 0x80 = WorkstationTrustAccount
2N/A * DWORD DomainSidSize; # domain sid length in bytes
2N/A * BYTE *DomainSid; # domain sid
2N/A * uint32_t NT_Version; # 0x00000001
2N/A * unsigned short LmNTToken; # 0xffff
2N/A * unsigned short Lm20Token; # 0xffff
2N/A * };
2N/A */
2N/Astatic void
2N/Asmb_netlogon_samlogon(struct name_entry *server,
2N/A char *mailbox,
2N/A char *domain,
2N/A smb_sid_t *domain_sid)
2N/A{
2N/A smb_msgbuf_t mb;
2N/A unsigned domain_sid_len;
2N/A char *username;
2N/A unsigned char buffer[MAX_DATAGRAM_LENGTH];
2N/A int offset;
2N/A int announce_len;
2N/A int data_length;
2N/A int name_length;
2N/A char hostname[NETBIOS_NAME_SZ];
2N/A
2N/A syslog(LOG_DEBUG, "NetLogonSamLogonReq: %s", domain);
2N/A
2N/A if (smb_getnetbiosname(hostname, sizeof (hostname)) != 0)
2N/A return;
2N/A
2N/A /*
2N/A * The username will be the trust account name on the PDC.
2N/A */
2N/A name_length = strlen(hostname) + 2;
2N/A username = alloca(name_length);
2N/A (void) snprintf(username, name_length, "%s$", hostname);
2N/A
2N/A domain_sid_len = smb_sid_len(domain_sid);
2N/A /*
2N/A * Add 2 to wide-char equivalent strlen to cover the
2N/A * two zero bytes that terminate the wchar string.
2N/A */
2N/A name_length = strlen(mailbox)+1;
2N/A
2N/A data_length = sizeof (short)
2N/A + sizeof (short)
2N/A + smb_wcequiv_strlen(hostname) + 2
2N/A + smb_wcequiv_strlen(username) + 2
2N/A + name_length
2N/A + sizeof (long)
2N/A + sizeof (long)
2N/A + domain_sid_len + 3 /* padding */
2N/A + sizeof (long)
2N/A + sizeof (short)
2N/A + sizeof (short);
2N/A
2N/A offset = smb_browser_load_transact_header(buffer,
2N/A sizeof (buffer), data_length, ONE_WAY_TRANSACTION,
2N/A MAILSLOT_NTLOGON);
2N/A
2N/A if (offset < 0) {
2N/A syslog(LOG_ERR, "NetLogonSamLogonReq: header error");
2N/A return;
2N/A }
2N/A
2N/A /*
2N/A * The domain SID is padded with 3 leading zeros.
2N/A */
2N/A smb_msgbuf_init(&mb, buffer + offset, sizeof (buffer) - offset, 0);
2N/A announce_len = smb_msgbuf_encode(&mb, "wwUUsll3.#clww",
2N/A (short)LOGON_SAM_LOGON_REQUEST,
2N/A 0, /* RequestCount */
2N/A hostname, /* UnicodeComputerName */
2N/A username, /* UnicodeUserName */
2N/A mailbox, /* MailslotName */
2N/A 0x00000080, /* AllowableAccountControlBits */
2N/A domain_sid_len, /* DomainSidSize */
2N/A domain_sid_len, domain_sid, /* DomainSid */
2N/A 0x00000001, /* NT_Version */
2N/A 0xffff, /* LmNTToken */
2N/A 0xffff); /* Lm20Token */
2N/A
2N/A if (announce_len <= 0) {
2N/A syslog(LOG_ERR, "NetLogonSamLogonReq: encode error");
2N/A smb_msgbuf_term(&mb);
2N/A return;
2N/A }
2N/A
2N/A smb_netlogon_send(server, domain, buffer, offset + announce_len);
2N/A smb_msgbuf_term(&mb);
2N/A}
2N/A
2N/A
2N/A/*
2N/A * Send a query for each version of the protocol.
2N/A */
2N/Astatic void
2N/Asmb_netlogon_send(struct name_entry *name,
2N/A char *domain,
2N/A unsigned char *buffer,
2N/A int count)
2N/A{
2N/A static char suffix[] = { 0x1B, 0x1C };
2N/A struct name_entry dname;
2N/A struct name_entry *dest;
2N/A struct name_entry *dest_dup;
2N/A int i;
2N/A
2N/A for (i = 0; i < sizeof (suffix)/sizeof (suffix[0]); i++) {
2N/A smb_init_name_struct((unsigned char *)domain, suffix[i],
2N/A 0, 0, 0, 0, 0, &dname);
2N/A
2N/A syslog(LOG_DEBUG, "SmbNetlogonSend");
2N/A smb_netbios_name_logf(&dname);
2N/A if ((dest = smb_name_find_name(&dname)) != 0) {
2N/A dest_dup = smb_netbios_name_dup(dest, 1);
2N/A smb_name_unlock_name(dest);
2N/A if (dest_dup) {
2N/A (void) smb_netbios_datagram_send(name,
2N/A dest_dup, buffer, count);
2N/A free(dest_dup);
2N/A }
2N/A } else {
2N/A syslog(LOG_DEBUG,
2N/A "SmbNetlogonSend: could not find %s<0x%X>",
2N/A domain, suffix[i]);
2N/A }
2N/A }
2N/A}
2N/A
2N/A/*
2N/A * smb_netlogon_rdc_rsp
2N/A *
2N/A * This is where we process netlogon responses for the resource domain.
2N/A * The src_name is the real name of the remote machine.
2N/A */
2N/Astatic void
2N/Asmb_netlogon_rdc_rsp(char *src_name, uint32_t src_ipaddr)
2N/A{
2N/A static int initialized = 0;
2N/A uint32_t ipaddr;
2N/A uint32_t prefer_ipaddr;
2N/A char ipstr[INET_ADDRSTRLEN];
2N/A char srcip[INET_ADDRSTRLEN];
2N/A int rc;
2N/A
2N/A (void) inet_ntop(AF_INET, &src_ipaddr, srcip, INET_ADDRSTRLEN);
2N/A
2N/A rc = smb_config_getstr(SMB_CI_DOMAIN_SRV, ipstr, INET_ADDRSTRLEN);
2N/A if (rc == SMBD_SMF_OK) {
2N/A rc = inet_pton(AF_INET, ipstr, &prefer_ipaddr);
2N/A if (rc == 0)
2N/A prefer_ipaddr = 0;
2N/A
2N/A if (!initialized) {
2N/A syslog(LOG_DEBUG, "SMB DC Preference: %s", ipstr);
2N/A initialized = 1;
2N/A }
2N/A }
2N/A
2N/A (void) mutex_lock(&ntdomain_mtx);
2N/A syslog(LOG_DEBUG, "DC Offer [%s]: %s [%s]",
2N/A ntdomain_info.n_domain, src_name, srcip);
2N/A
2N/A if (ntdomain_info.n_ipaddr != 0) {
2N/A if (prefer_ipaddr != 0 &&
2N/A prefer_ipaddr == ntdomain_info.n_ipaddr) {
2N/A syslog(LOG_DEBUG, "DC for %s: %s [%s]",
2N/A ntdomain_info.n_domain, src_name, srcip);
2N/A (void) mutex_unlock(&ntdomain_mtx);
2N/A return;
2N/A }
2N/A
2N/A ipaddr = ntdomain_info.n_ipaddr;
2N/A } else
2N/A ipaddr = 0;
2N/A
2N/A if (smb_better_dc(ipaddr, src_ipaddr) ||
2N/A (prefer_ipaddr != 0 && prefer_ipaddr == src_ipaddr)) {
2N/A /* set nbtd cache */
2N/A (void) strlcpy(ntdomain_info.n_name, src_name,
2N/A SMB_PI_MAX_DOMAIN);
2N/A ntdomain_info.n_ipaddr = src_ipaddr;
2N/A (void) cond_broadcast(&ntdomain_cv);
2N/A syslog(LOG_DEBUG, "DC discovered for %s: %s [%s]",
2N/A ntdomain_info.n_domain, src_name, srcip);
2N/A }
2N/A (void) mutex_unlock(&ntdomain_mtx);
2N/A}
2N/A
2N/Astatic int
2N/Asmb_better_dc(uint32_t cur_ip, uint32_t new_ip)
2N/A{
2N/A smb_inaddr_t ipaddr;
2N/A
2N/A /*
2N/A * If we don't have any current DC,
2N/A * then use the new one of course.
2N/A */
2N/A
2N/A if (cur_ip == 0)
2N/A return (1);
2N/A /*
2N/A * see if there is a DC in the
2N/A * same subnet
2N/A */
2N/A
2N/A ipaddr.a_family = AF_INET;
2N/A ipaddr.a_ipv4 = cur_ip;
2N/A if (smb_nic_is_same_subnet(&ipaddr))
2N/A return (0);
2N/A
2N/A ipaddr.a_family = AF_INET;
2N/A ipaddr.a_ipv4 = new_ip;
2N/A if (smb_nic_is_same_subnet(&ipaddr))
2N/A return (1);
2N/A /*
2N/A * Otherwise, just keep the old one.
2N/A */
2N/A return (0);
2N/A}