summaryrefslogtreecommitdiff
path: root/usr/src/lib/smbsrv/libsmbns/common/smbns_netlogon.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/lib/smbsrv/libsmbns/common/smbns_netlogon.c')
-rw-r--r--usr/src/lib/smbsrv/libsmbns/common/smbns_netlogon.c643
1 files changed, 643 insertions, 0 deletions
diff --git a/usr/src/lib/smbsrv/libsmbns/common/smbns_netlogon.c b/usr/src/lib/smbsrv/libsmbns/common/smbns_netlogon.c
new file mode 100644
index 0000000000..d74bcb168d
--- /dev/null
+++ b/usr/src/lib/smbsrv/libsmbns/common/smbns_netlogon.c
@@ -0,0 +1,643 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * This module handles the primary domain controller location protocol.
+ * The document claims to be version 1.15 of the browsing protocol. It also
+ * claims to specify the mailslot protocol.
+ *
+ * The NETLOGON protocol uses \MAILSLOT\NET mailslots. The protocol
+ * specification is incomplete, contains errors and is out-of-date but
+ * it does provide some useful background information. The document
+ * doesn't mention the NETLOGON_SAMLOGON version of the protocol.
+ */
+
+#include <stdlib.h>
+#include <syslog.h>
+#include <alloca.h>
+#include <arpa/inet.h>
+#include <resolv.h>
+
+#include <smbsrv/mailslot.h>
+#include <smbsrv/libsmbns.h>
+#include <smbns_ads.h>
+#include <smbns_browser.h>
+#include <smbns_netbios.h>
+
+static void smb_netlogon_query(struct name_entry *server, char *mailbox,
+ char *domain);
+
+static void smb_netlogon_samlogon(struct name_entry *server, char *mailbox,
+ char *domain);
+
+static void smb_netlogon_send(struct name_entry *name, char *domain,
+ unsigned char *buffer, int count);
+
+static void smb_netlogon_rdc_rsp(char *src_name, uint32_t src_ipaddr);
+static int better_dc(uint32_t cur_ip, uint32_t new_ip);
+
+static char resource_domain[SMB_PI_MAX_DOMAIN];
+
+/*
+ * smb_netlogon_request
+ *
+ * This is the entry point locating the resource domain PDC. A netlogon
+ * request is sent using the specified protocol on the specified network.
+ * Note that we need to know the domain SID in order to use the samlogon
+ * format.
+ *
+ * Netlogon responses are received asynchronously and eventually handled
+ * in smb_netlogon_receive.
+ */
+void
+smb_netlogon_request(int net, int protocol, char *domain)
+{
+ struct name_entry *server;
+ nt_domain_t *ntdp;
+
+ server = smb_browser_get_srvname(net);
+ if (server == 0)
+ return;
+
+ (void) strlcpy(resource_domain, domain,
+ sizeof (resource_domain));
+
+ if (strlen(resource_domain) > 0) {
+ ntdp = nt_domain_lookup_name(resource_domain);
+ if (protocol == NETLOGON_PROTO_SAMLOGON && ntdp)
+ smb_netlogon_samlogon(server,
+ MAILSLOT_NETLOGON_SAMLOGON_RDC,
+ resource_domain);
+ else
+ smb_netlogon_query(server,
+ MAILSLOT_NETLOGON_RDC,
+ resource_domain);
+ }
+}
+
+/*
+ * smb_netlogon_receive
+ *
+ * This is where we handle all incoming NetLogon messages. Currently, we
+ * ignore requests from anyone else. We are only interested in responses
+ * to our own requests. The NetLogonResponse provides the name of the PDC.
+ * If we don't already have a controller name, we use the name provided
+ * in the message. Otherwise we use the name already in the environment.
+ */
+void
+smb_netlogon_receive(struct datagram *datagram,
+ char *mailbox,
+ unsigned char *data,
+ int datalen)
+{
+ struct netlogon_opt {
+ char *mailslot;
+ void (*handler)();
+ } netlogon_opt[] = {
+ { MAILSLOT_NETLOGON_RDC, smb_netlogon_rdc_rsp },
+ { MAILSLOT_NETLOGON_SAMLOGON_RDC, smb_netlogon_rdc_rsp },
+ };
+
+ smb_msgbuf_t mb;
+ unsigned short opcode;
+ char src_name[SMB_PI_MAX_HOST];
+ mts_wchar_t unicode_src_name[SMB_PI_MAX_HOST];
+ unsigned int cpid = oem_get_smb_cpid();
+ uint32_t src_ipaddr;
+ char *junk;
+ char *primary;
+ char *domain;
+ int i;
+ char ipstr[16];
+ int rc;
+
+ src_ipaddr = datagram->src.addr_list.sin.sin_addr.s_addr;
+
+ /*
+ * The datagram->src.name is in oem codepage format.
+ * Therefore, we need to convert it to unicode and
+ * store it in multi-bytes format.
+ */
+ (void) oemstounicodes(unicode_src_name, (char *)datagram->src.name,
+ SMB_PI_MAX_HOST, cpid);
+ (void) mts_wcstombs(src_name, unicode_src_name, SMB_PI_MAX_HOST);
+
+ (void) trim_whitespace(src_name);
+
+ (void) inet_ntop(AF_INET, (const void *)(&src_ipaddr), ipstr,
+ sizeof (ipstr));
+ syslog(LOG_DEBUG, "NetLogonReceive: src=%s [%s], mbx=%s",
+ src_name, ipstr, mailbox);
+
+ smb_msgbuf_init(&mb, data, datalen, 0);
+
+ if (smb_msgbuf_decode(&mb, "w", &opcode) < 0) {
+ syslog(LOG_ERR, "NetLogonReceive: decode error");
+ smb_msgbuf_term(&mb);
+ return;
+ }
+
+ switch (opcode) {
+ case LOGON_PRIMARY_RESPONSE:
+ /*
+ * Message contains:
+ * PDC name (MBS), PDC name (Unicode), Domain name (unicode)
+ */
+ rc = smb_msgbuf_decode(&mb, "sUU", &junk, &primary, &domain);
+ if (rc < 0) {
+ syslog(LOG_ERR,
+ "NetLogonResponse: opcode %d decode error",
+ opcode);
+ smb_msgbuf_term(&mb);
+ return;
+ }
+ break;
+
+ case LOGON_SAM_LOGON_RESPONSE:
+ case LOGON_SAM_USER_UNKNOWN:
+ /*
+ * Message contains:
+ * PDC name, User name, Domain name (all unicode)
+ */
+ rc = smb_msgbuf_decode(&mb, "UUU", &primary, &junk, &domain);
+ if (rc < 0) {
+ syslog(LOG_ERR,
+ "NetLogonResponse: opcode %d decode error",
+ opcode);
+ smb_msgbuf_term(&mb);
+ return;
+ }
+
+ /*
+ * skip past the "\\" prefix
+ */
+ primary += strspn(primary, "\\");
+ break;
+
+ default:
+ /*
+ * We don't respond to PDC discovery requests.
+ */
+ syslog(LOG_DEBUG, "NetLogonReceive: opcode 0x%04x", opcode);
+ smb_msgbuf_term(&mb);
+ return;
+ }
+
+ if (domain == 0 || primary == 0) {
+ syslog(LOG_ERR, "NetLogonResponse: malformed packet");
+ smb_msgbuf_term(&mb);
+ return;
+ }
+
+ syslog(LOG_DEBUG, "DC Offer Dom=%s PDC=%s From=%s",
+ domain, primary, src_name);
+
+ if (strcasecmp(domain, resource_domain)) {
+ syslog(LOG_DEBUG, "NetLogonResponse: other domain "
+ "%s, requested %s", domain, resource_domain);
+ smb_msgbuf_term(&mb);
+ return;
+ }
+
+ for (i = 0; i < sizeof (netlogon_opt)/sizeof (netlogon_opt[0]); ++i) {
+ if (strcasecmp(netlogon_opt[i].mailslot, mailbox) == 0) {
+ syslog(LOG_DEBUG, "NetLogonReceive: %s", mailbox);
+ (*netlogon_opt[i].handler)(primary, src_ipaddr);
+ smb_msgbuf_term(&mb);
+ return;
+ }
+ }
+
+ syslog(LOG_DEBUG, "NetLogonReceive[%s]: unknown mailslot", mailbox);
+ smb_msgbuf_term(&mb);
+}
+
+
+
+/*
+ * smb_netlogon_query
+ *
+ * Build and send a LOGON_PRIMARY_QUERY to the MAILSLOT_NETLOGON. At some
+ * point we should receive a LOGON_PRIMARY_RESPONSE in the mailslot we
+ * specify in the request.
+ *
+ * struct NETLOGON_QUERY {
+ * unsigned short Opcode; # LOGON_PRIMARY_QUERY
+ * char ComputerName[]; # ASCII hostname. The response
+ * # is sent to <ComputerName>(00).
+ * char MailslotName[]; # MAILSLOT_NETLOGON
+ * char Pad[]; # Pad to short
+ * wchar_t ComputerName[] # UNICODE hostname
+ * DWORD NT_Version; # 0x00000001
+ * WORD LmNTToken; # 0xffff
+ * WORD Lm20Token; # 0xffff
+ * };
+ */
+static void
+smb_netlogon_query(struct name_entry *server,
+ char *mailbox,
+ char *domain)
+{
+ smb_msgbuf_t mb;
+ int offset, announce_len, data_length, name_lengths;
+ unsigned char buffer[MAX_DATAGRAM_LENGTH];
+ char hostname[MAXHOSTNAMELEN];
+
+ if (smb_gethostname(hostname, MAXHOSTNAMELEN, 1) != 0)
+ return;
+
+ name_lengths = strlen(mailbox)+1+strlen(hostname)+1;
+
+ /*
+ * The (name_lengths & 1) part is to word align the name_lengths
+ * before the wc equiv strlen and the "+ 2" is to cover the two
+ * zero bytes that terminate the wchar string.
+ */
+ data_length = sizeof (short) + name_lengths + (name_lengths & 1) +
+ mts_wcequiv_strlen(hostname) + 2 + sizeof (long) + sizeof (short) +
+ sizeof (short);
+
+ offset = smb_browser_load_transact_header(buffer,
+ sizeof (buffer), data_length, ONE_WAY_TRANSACTION,
+ MAILSLOT_NETLOGON);
+
+ if (offset < 0)
+ return;
+
+ smb_msgbuf_init(&mb, buffer + offset, sizeof (buffer) - offset, 0);
+
+ announce_len = smb_msgbuf_encode(&mb, "wssUlww",
+ (short)LOGON_PRIMARY_QUERY,
+ hostname,
+ mailbox,
+ hostname,
+ 0x1,
+ 0xffff,
+ 0xffff);
+
+ if (announce_len <= 0) {
+ smb_msgbuf_term(&mb);
+ syslog(LOG_ERR, "NetLogonQuery: encode error");
+ return;
+ }
+
+ smb_netlogon_send(server, domain, buffer, offset + announce_len);
+ smb_msgbuf_term(&mb);
+}
+
+
+/*
+ * smb_netlogon_samlogon
+ *
+ * The SamLogon version of the NetLogon request uses the workstation trust
+ * account and, I think, may be a prerequisite to the challenge/response
+ * netr authentication. The trust account username is the hostname with a
+ * $ appended. The mailslot for this request is MAILSLOT_NTLOGON. At some
+ * we should receive a LOGON_SAM_LOGON_RESPONSE in the mailslot we
+ * specify in the request.
+ *
+ * struct NETLOGON_SAM_LOGON {
+ * unsigned short Opcode; # LOGON_SAM_LOGON_REQUEST
+ * unsigned short RequestCount; # 0
+ * wchar_t UnicodeComputerName; # hostname
+ * wchar_t UnicodeUserName; # hostname$
+ * char *MailslotName; # response mailslot
+ * DWORD AllowableAccountControlBits; # 0x80 = WorkstationTrustAccount
+ * DWORD DomainSidSize; # domain sid length in bytes
+ * BYTE *DomainSid; # domain sid
+ * uint32_t NT_Version; # 0x00000001
+ * unsigned short LmNTToken; # 0xffff
+ * unsigned short Lm20Token; # 0xffff
+ * };
+ */
+static void
+smb_netlogon_samlogon(struct name_entry *server,
+ char *mailbox,
+ char *domain)
+{
+ smb_msgbuf_t mb;
+ nt_domain_t *ntdp;
+ nt_sid_t *domain_sid;
+ unsigned domain_sid_len;
+ char *username;
+ unsigned char buffer[MAX_DATAGRAM_LENGTH];
+ int offset;
+ int announce_len;
+ int data_length;
+ int name_length;
+ char hostname[MAXHOSTNAMELEN];
+
+ syslog(LOG_DEBUG, "NetLogonSamLogonReq: %s", domain);
+
+ if ((ntdp = nt_domain_lookup_name(domain)) == 0) {
+ syslog(LOG_ERR, "NetLogonSamLogonReq[%s]: no sid", domain);
+ return;
+ }
+
+ domain_sid = ntdp->sid;
+ domain_sid_len = nt_sid_length(domain_sid);
+ nt_sid_logf(domain_sid);
+
+ if (smb_gethostname(hostname, MAXHOSTNAMELEN, 1) != 0)
+ return;
+
+ /*
+ * The username will be the trust account name on the PDC.
+ */
+ name_length = strlen(hostname) + 2;
+ username = alloca(name_length);
+ (void) snprintf(username, name_length, "%s$", hostname);
+
+ /*
+ * Add 2 to wide-char equivalent strlen to cover the
+ * two zero bytes that terminate the wchar string.
+ */
+ name_length = strlen(mailbox)+1;
+
+ data_length = sizeof (short)
+ + sizeof (short)
+ + mts_wcequiv_strlen(hostname) + 2
+ + mts_wcequiv_strlen(username) + 2
+ + name_length
+ + sizeof (long)
+ + sizeof (long)
+ + domain_sid_len + 3 /* padding */
+ + sizeof (long)
+ + sizeof (short)
+ + sizeof (short);
+
+ offset = smb_browser_load_transact_header(buffer,
+ sizeof (buffer), data_length, ONE_WAY_TRANSACTION,
+ MAILSLOT_NTLOGON);
+
+ if (offset < 0) {
+ syslog(LOG_ERR, "NetLogonSamLogonReq: header error");
+ return;
+ }
+
+ /*
+ * The domain SID is padded with 3 leading zeros.
+ */
+ smb_msgbuf_init(&mb, buffer + offset, sizeof (buffer) - offset, 0);
+ announce_len = smb_msgbuf_encode(&mb, "wwUUsll3.#clww",
+ (short)LOGON_SAM_LOGON_REQUEST,
+ 0, /* RequestCount */
+ hostname, /* UnicodeComputerName */
+ username, /* UnicodeUserName */
+ mailbox, /* MailslotName */
+ 0x00000080, /* AllowableAccountControlBits */
+ domain_sid_len, /* DomainSidSize */
+ domain_sid_len, domain_sid, /* DomainSid */
+ 0x00000001, /* NT_Version */
+ 0xffff, /* LmNTToken */
+ 0xffff); /* Lm20Token */
+
+ if (announce_len <= 0) {
+ syslog(LOG_ERR, "NetLogonSamLogonReq: encode error");
+ smb_msgbuf_term(&mb);
+ return;
+ }
+
+ smb_netlogon_send(server, domain, buffer, offset + announce_len);
+ smb_msgbuf_term(&mb);
+}
+
+
+/*
+ * Send a query for each version of the protocol.
+ */
+static void
+smb_netlogon_send(struct name_entry *name,
+ char *domain,
+ unsigned char *buffer,
+ int count)
+{
+ static char suffix[] = { 0x1B, 0x1C };
+ struct name_entry dname;
+ struct name_entry *dest;
+ struct name_entry *dest_dup;
+ int i;
+
+ for (i = 0; i < sizeof (suffix)/sizeof (suffix[0]); i++) {
+ smb_init_name_struct((unsigned char *)domain, suffix[i],
+ 0, 0, 0, 0, 0, &dname);
+
+ syslog(LOG_DEBUG, "smb_netlogon_send");
+ smb_netbios_name_dump(&dname);
+ if ((dest = smb_name_find_name(&dname)) != 0) {
+ dest_dup = smb_netbios_name_dup(dest, 1);
+ smb_name_unlock_name(dest);
+ if (dest_dup) {
+ (void) smb_netbios_datagram_send(name, dest_dup,
+ buffer, count);
+ free(dest_dup);
+ }
+ } else {
+ syslog(LOG_DEBUG, "smbd: NBNS couldn't find %s<0x%X>",
+ domain, suffix[i]);
+ }
+ }
+}
+
+/*
+ * smb_netlogon_rdc_rsp
+ *
+ * This is where we process netlogon responses for the resource domain.
+ * The src_name is the real name of the remote machine.
+ */
+static void
+smb_netlogon_rdc_rsp(char *src_name, uint32_t src_ipaddr)
+{
+ static int initialized = 0;
+ smb_ntdomain_t *pi;
+ uint32_t ipaddr;
+ uint32_t prefer_ipaddr = 0;
+ char ipstr[16];
+ char srcip[16];
+ char *p;
+ int rc;
+
+ (void) inet_ntop(AF_INET, (const void *)(&src_ipaddr),
+ srcip, sizeof (srcip));
+
+ smb_config_rdlock();
+ if ((p = smb_config_get(SMB_CI_DOMAIN_SRV)) != 0) {
+ rc = inet_pton(AF_INET, p, &prefer_ipaddr);
+ if (rc == 0)
+ prefer_ipaddr = 0;
+
+ if (!initialized) {
+ (void) inet_ntop(AF_INET,
+ (const void *)(&prefer_ipaddr),
+ ipstr, sizeof (ipstr));
+ syslog(LOG_DEBUG, "SMB DC Preference: %s", ipstr);
+ initialized = 1;
+ }
+ }
+ smb_config_unlock();
+
+ syslog(LOG_DEBUG, "DC Offer [%s]: %s [%s]",
+ resource_domain, src_name, srcip);
+
+ if ((pi = smb_getdomaininfo(0)) != 0) {
+ if (prefer_ipaddr != 0 && prefer_ipaddr == pi->ipaddr) {
+ syslog(LOG_DEBUG, "DC for %s: %s [%s]",
+ resource_domain, src_name, srcip);
+ return;
+ }
+
+ ipaddr = pi->ipaddr;
+ } else
+ ipaddr = 0;
+
+ if (better_dc(ipaddr, src_ipaddr) ||
+ (prefer_ipaddr != 0 && prefer_ipaddr == src_ipaddr)) {
+ smb_setdomaininfo(resource_domain, src_name,
+ src_ipaddr);
+ syslog(LOG_DEBUG, "DC discovered for %s: %s [%s]",
+ resource_domain, src_name, srcip);
+ }
+}
+
+static int
+better_dc(uint32_t cur_ip, uint32_t new_ip)
+{
+ net_cfg_t cfg;
+
+ /*
+ * If we don't have any current DC,
+ * then use the new one of course.
+ */
+ if (cur_ip == 0)
+ return (1);
+
+ if (smb_nic_get_bysubnet(cur_ip, &cfg) != NULL)
+ return (0);
+ if (smb_nic_get_bysubnet(new_ip, &cfg) != NULL)
+ return (1);
+ /*
+ * Otherwise, just keep the old one.
+ */
+ return (0);
+}
+
+/*
+ * msdcs_lookup_ads
+ *
+ * Try to find a domain controller in ADS. Actually we want to query DNS
+ * but we need to find out if ADS is enabled and this is probably the
+ * best way. The IP address isn't set up in the ADS_HANDLE so we need to
+ * make the ads_find_host call. This will only succeed if ADS is enabled.
+ *
+ * Returns 1 if a domain controller was found and its name and IP address
+ * have been updated. Otherwise returns 0.
+ */
+int
+msdcs_lookup_ads(void)
+{
+ ADS_HOST_INFO *hinfo = 0;
+ int ads_port = 0;
+ char ads_domain[MAXHOSTNAMELEN];
+ char site_service[MAXHOSTNAMELEN];
+ char service[MAXHOSTNAMELEN];
+ char *site;
+ char *p;
+ char *ip_addr;
+ struct in_addr ns_list[MAXNS];
+ int i, cnt, go_next;
+
+ if (smb_getdomainname(ads_domain, MAXHOSTNAMELEN) != 0)
+ return (0);
+
+ /*
+ * Initialize the NT domain name.
+ */
+ (void) strlcpy(resource_domain, ads_domain, SMB_PI_MAX_DOMAIN);
+ if ((p = strchr(resource_domain, '.')) != 0)
+ *p = '\0';
+
+ smb_config_rdlock();
+ if (smb_config_getyorn(SMB_CI_MSDCS_DISABLE) != 0) {
+ /*
+ * The system administrator doesn't
+ * want to use ADS to find the PDC.
+ */
+ syslog(LOG_DEBUG, "msdcsLookupADS: disabled");
+ smb_config_unlock();
+ return (0);
+ }
+ site = smb_config_getstr(SMB_CI_ADS_SITE);
+ smb_config_unlock();
+
+ syslog(LOG_DEBUG, "msdcsLookupADS %s, MAXHOSTNAMELEN=%d",
+ ads_domain, MAXHOSTNAMELEN);
+ if (site && *site != 0) {
+ (void) snprintf(site_service, MAXHOSTNAMELEN,
+ "_ldap._tcp.%s._sites.dc._msdcs.%s",
+ site, ads_domain);
+ }
+
+ (void) snprintf(service, MAXHOSTNAMELEN,
+ "_ldap._tcp.dc._msdcs.%s", ads_domain);
+
+ cnt = smb_get_nameservers(ns_list, MAXNS);
+
+ go_next = 0;
+ for (i = 0; i < cnt; i++) {
+ ip_addr = inet_ntoa(ns_list[i]);
+
+ hinfo = ads_find_host(ip_addr, ads_domain, &ads_port,
+ site_service, &go_next);
+
+ if (hinfo == NULL) {
+ hinfo = ads_find_host(ip_addr, ads_domain, &ads_port,
+ service, &go_next);
+ }
+
+ if ((hinfo != NULL) || (go_next == 0))
+ break;
+ }
+
+ if (hinfo == NULL) {
+ syslog(LOG_DEBUG, "msdcsLookupADS: unable to find host");
+ return (0);
+ }
+
+ syslog(LOG_DEBUG, "msdcsLookupADS: %s [%I]", hinfo->name,
+ hinfo->ip_addr);
+
+ /*
+ * Remove the domain extension - the
+ * NetBIOS browser can't handle it.
+ */
+ if ((p = strchr(hinfo->name, '.')) != 0)
+ *p = '\0';
+
+ smb_netlogon_rdc_rsp(hinfo->name, hinfo->ip_addr);
+
+ return (1);
+}