# # 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. # # ident "%Z%%M% %I% %E% SMI" # HDRS= libsmbns.h include ../Makefile.smbsrv # # 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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. # Copyright 2014 Nexenta Systems, Inc. All rights reserved. # # Copyright (c) 2018, Joyent, Inc. LIBRARY= libsmbns.a VERS= .1 OBJS_SHARED = \ smb_netbios_util.o \ OBJS_COMMON= \ smbns_ads.o \ smbns_browser.o \ smbns_dyndns.o \ smbns_krb.o \ smbns_ksetpwd.o \ smbns_netbios.o \ smbns_netbios_cache.o \ smbns_netbios_datagram.o\ smbns_netbios_name.o \ smbns_netlogon.o OBJECTS= $(OBJS_COMMON) $(OBJS_SHARED) include ../../../Makefile.lib include ../../Makefile.lib # Hammerhead: Include mech_krb5 definitions for direct linking. # libkrb5.so is a filter library; GNU ld cannot resolve symbols through it. include $(SRC)/lib/gss_mechs/mech_krb5/Makefile.mech_krb5 SRCS= $(OBJS_COMMON:%.o=$(SRCDIR)/%.c) \ $(OBJS_SHARED:%.o=$(SRC)/common/smbsrv/%.c) LDLIBS += $(MACH_LDLIBS) LDLIBS += -lsmb -lads -lgss -lcmdutils -lldap \ -lsocket -lnsl -lc CPPFLAGS += -D_REENTRANT CPPFLAGS += -Dsyslog=smb_syslog CERRWARN += -Wno-unused-function CERRWARN += $(CNOWARN_UNINIT) # not linted SMATCH=off # Hammerhead: Link directly against mech_krb5.so instead of filter lib -lkrb5 $(DYNLIB) : LDLIBS += $(KMECHLIB) $(DYNLIB) : DYNFLAGS += $(KRUNPATH) include ../../Makefile.targ include ../../../Makefile.targ # # 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. # # Hammerhead: 64-bit only - flatten smbsrv library paths MACH_LDLIBS += -L$(ROOT)/usr/lib/smbsrv include ../Makefile.com include ../../../Makefile.lib.64 DYNFLAGS += -R/usr/lib/smbsrv install: all $(ROOTLIBS64) $(ROOTLINKS64) /* * 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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2019 Nexenta by DDN, Inc. All rights reserved. * Copyright 2024 RackTop Systems, Inc. */ #ifndef _LIBSMBNS_H #define _LIBSMBNS_H #include #include #ifdef __cplusplus extern "C" { #endif /* ADS typedef/data structures and functions */ typedef struct smb_ads_handle { char *domain; /* ADS domain (in lower case) */ char *domain_dn; /* domain in Distinquish Name format */ char *ip_addr; /* ip addr in string format */ char *hostname; /* fully qualified hostname */ char *site; /* local ADS site */ LDAP *ld; /* LDAP handle */ } smb_ads_handle_t; typedef struct smb_ads_host_info { char name[MAXHOSTNAMELEN]; /* fully qualified hostname */ int port; /* ldap port */ int priority; /* DNS SRV record priority */ int weight; /* DNS SRV record weight */ uint32_t flags; /* DC flags */ smb_inaddr_t ipaddr; /* network byte order */ } smb_ads_host_info_t; /* * Return status codes for the ads functions. */ typedef enum smb_ads_status { SMB_ADS_SUCCESS = 0, /* errno values... */ SMB_ADS_ERRNO_GAP = 200, SMB_ADS_KRB5_INIT_CTX, SMB_ADS_KRB5_CC_DEFAULT, SMB_ADS_KRB5_PARSE_PRINCIPAL, SMB_ADS_KRB5_GET_INIT_CREDS_OTHER, SMB_ADS_KRB5_GET_INIT_CREDS_PW, SMB_ADS_KRB5_GET_INIT_CREDS_SKEW, SMB_ADS_KRB5_CC_INITIALIZE, SMB_ADS_KRB5_CC_STORE_CRED, SMB_ADS_CANT_LOCATE_DC, SMB_ADS_LDAP_INIT, SMB_ADS_LDAP_SETOPT, SMB_ADS_LDAP_SET_DOM, SMB_ADS_LDAP_SASL_BIND, SMB_ADJOIN_ERR_GEN_PWD, SMB_ADJOIN_ERR_GET_DCLEVEL, SMB_ADJOIN_ERR_ADD_TRUST_ACCT, SMB_ADJOIN_ERR_MOD_TRUST_ACCT, SMB_ADJOIN_ERR_DUP_TRUST_ACCT, SMB_ADJOIN_ERR_TRUST_ACCT, SMB_ADJOIN_ERR_INIT_KRB_CTX, SMB_ADJOIN_ERR_GET_SPNS, SMB_ADJOIN_ERR_KSETPWD, SMB_ADJOIN_ERR_UPDATE_CNTRL_ATTR, SMB_ADJOIN_ERR_WRITE_KEYTAB, SMB_ADJOIN_ERR_IDMAP_SET_DOMAIN, SMB_ADJOIN_ERR_IDMAP_REFRESH, SMB_ADJOIN_ERR_COMMIT_KEYTAB, SMB_ADJOIN_ERR_AUTH_NETLOGON, SMB_ADJOIN_ERR_STORE_PROPS, SMB_ADJOIN_ERR_ALREADY_JOINED, } smb_ads_status_t; /* ADS functions */ extern void smb_ads_init(void); extern void smb_ads_fini(void); extern void smb_ads_refresh(boolean_t); extern smb_ads_handle_t *smb_ads_open(void); extern void smb_ads_close(smb_ads_handle_t *); extern int smb_ads_publish_share(smb_ads_handle_t *, const char *, const char *, const char *, const char *); extern int smb_ads_remove_share(smb_ads_handle_t *, const char *, const char *, const char *, const char *); extern int smb_ads_build_unc_name(char *, int, const char *, const char *); extern int smb_ads_lookup_share(smb_ads_handle_t *, const char *, const char *, char *); extern int smb_ads_add_share(smb_ads_handle_t *, const char *, const char *, const char *); extern smb_ads_status_t smb_ads_join(char *, char *, char *, char *, char *); extern void smb_ads_log_errmsg(smb_ads_status_t); extern const char *smb_ads_strerror(int); extern uint32_t smb_ads_lookup_msdcs(char *, smb_dcinfo_t *); extern smb_ads_host_info_t *smb_ads_find_host(char *); /* DYNDNS functions */ extern void *dyndns_publisher(void *); extern void dyndns_start(void); extern void dyndns_stop(void); extern int dyndns_update(char *); extern void dyndns_update_zones(void); extern void dyndns_clear_zones(void); /* Kerberos cache management function */ extern int smb_ccache_init(char *, char *); extern void smb_ccache_remove(char *); /* NETBIOS Functions */ extern int smb_netbios_start(void); extern void smb_netbios_stop(void); extern void smb_netbios_name_reconfig(void); /* Browser Functions */ extern void smb_browser_reconfig(void); extern boolean_t smb_browser_netlogon(char *, char *, uint32_t); #ifdef __cplusplus } #endif #endif /* _LIBSMBNS_H */ # # 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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. # Copyright 2014 Nexenta Systems, Inc. All rights reserved. # # # MAPFILE HEADER START # # WARNING: STOP NOW. DO NOT MODIFY THIS FILE. # Object versioning must comply with the rules detailed in # # usr/src/lib/README.mapfiles # # You should not be making modifications here until you've read the most current # copy of that file. If you need help, contact a gatekeeper for guidance. # # MAPFILE HEADER END # $mapfile_version 2 SYMBOL_VERSION SUNWprivate { global: dyndns_clear_zones; dyndns_publisher; dyndns_start; dyndns_stop; dyndns_update; dyndns_update_zones; smb_ads_add_share; smb_ads_build_unc_name; smb_ads_close; smb_ads_find_host; smb_ads_fini; smb_ads_init; smb_ads_join; smb_ads_log_errmsg; smb_ads_lookup_msdcs; smb_ads_lookup_share; smb_ads_open; smb_ads_publish_share; smb_ads_refresh; smb_ads_remove_share; smb_ads_strerror; smb_browser_netlogon; smb_browser_reconfig; smb_ccache_init; smb_ccache_remove; smb_netbios_name_reconfig; smb_netbios_start; smb_netbios_stop; local: *; }; /* * 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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2022 Tintri by DDN, Inc. All rights reserved. * Copyright 2021-2024 RackTop Systems, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SMB_ADS_AF_UNKNOWN(x) (((x)->ipaddr.a_family != AF_INET) && \ ((x)->ipaddr.a_family != AF_INET6)) #define SMB_ADS_MAXBUFLEN 100 #define SMB_ADS_DN_MAX 300 #define SMB_ADS_MAXMSGLEN 512 #define SMB_ADS_COMPUTERS_CN "Computers" #define SMB_ADS_COMPUTER_NUM_ATTR 8 #define SMB_ADS_SHARE_NUM_ATTR 3 #define SMB_ADS_SITE_MAX MAXHOSTNAMELEN #define SMB_ADS_MSDCS_SRV_DC_RR "_ldap._tcp.dc._msdcs" #define SMB_ADS_MSDCS_SRV_SITE_RR "_ldap._tcp.%s._sites.dc._msdcs" /* * domainControllerFunctionality * * This rootDSE attribute indicates the functional level of the DC. */ #define SMB_ADS_ATTR_DCLEVEL "domainControllerFunctionality" #define SMB_ADS_DCLEVEL_W2K 0 #define SMB_ADS_DCLEVEL_W2K3 2 #define SMB_ADS_DCLEVEL_W2K8 3 #define SMB_ADS_DCLEVEL_W2K8_R2 4 /* * msDs-supportedEncryptionTypes (Windows Server 2008 only) * * This attribute defines the encryption types supported by the system. * Encryption Types: * - DES cbc mode with CRC-32 * - DES cbc mode with RSA-MD5 * - ArcFour with HMAC/md5 * - AES-128 * - AES-256 */ #define SMB_ADS_ATTR_ENCTYPES "msDs-supportedEncryptionTypes" #define SMB_ADS_ENC_DES_CRC 1 #define SMB_ADS_ENC_DES_MD5 2 #define SMB_ADS_ENC_RC4 4 #define SMB_ADS_ENC_AES128 8 #define SMB_ADS_ENC_AES256 16 static krb5_enctype w2k8enctypes[] = { ENCTYPE_AES256_CTS_HMAC_SHA1_96, ENCTYPE_AES128_CTS_HMAC_SHA1_96, ENCTYPE_ARCFOUR_HMAC, ENCTYPE_DES_CBC_CRC, ENCTYPE_DES_CBC_MD5, }; static krb5_enctype pre_w2k8enctypes[] = { ENCTYPE_ARCFOUR_HMAC, ENCTYPE_DES_CBC_CRC, ENCTYPE_DES_CBC_MD5, }; #define SMB_ADS_ATTR_SAMACCT "sAMAccountName" #define SMB_ADS_ATTR_UPN "userPrincipalName" #define SMB_ADS_ATTR_SPN "servicePrincipalName" #define SMB_ADS_ATTR_CTL "userAccountControl" #define SMB_ADS_ATTR_UCPWD "unicodePwd" #define SMB_ADS_ATTR_DNSHOST "dNSHostName" #define SMB_ADS_ATTR_KVNO "msDS-KeyVersionNumber" #define SMB_ADS_ATTR_DN "distinguishedName" /* * UserAccountControl flags: manipulate user account properties. * * The hexadecimal value of the following property flags are based on MSDN * article # 305144. */ #define SMB_ADS_USER_ACCT_CTL_SCRIPT 0x00000001 #define SMB_ADS_USER_ACCT_CTL_ACCOUNTDISABLE 0x00000002 #define SMB_ADS_USER_ACCT_CTL_HOMEDIR_REQUIRED 0x00000008 #define SMB_ADS_USER_ACCT_CTL_LOCKOUT 0x00000010 #define SMB_ADS_USER_ACCT_CTL_PASSWD_NOTREQD 0x00000020 #define SMB_ADS_USER_ACCT_CTL_PASSWD_CANT_CHANGE 0x00000040 #define SMB_ADS_USER_ACCT_CTL_ENCRYPTED_TEXT_PWD_ALLOWED 0x00000080 #define SMB_ADS_USER_ACCT_CTL_TMP_DUP_ACCT 0x00000100 #define SMB_ADS_USER_ACCT_CTL_NORMAL_ACCT 0x00000200 #define SMB_ADS_USER_ACCT_CTL_INTERDOMAIN_TRUST_ACCT 0x00000800 #define SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT 0x00001000 #define SMB_ADS_USER_ACCT_CTL_SRV_TRUST_ACCT 0x00002000 #define SMB_ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD 0x00010000 #define SMB_ADS_USER_ACCT_CTL_MNS_LOGON_ACCT 0x00020000 #define SMB_ADS_USER_ACCT_CTL_SMARTCARD_REQUIRED 0x00040000 #define SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION 0x00080000 #define SMB_ADS_USER_ACCT_CTL_NOT_DELEGATED 0x00100000 #define SMB_ADS_USER_ACCT_CTL_USE_DES_KEY_ONLY 0x00200000 #define SMB_ADS_USER_ACCT_CTL_DONT_REQ_PREAUTH 0x00400000 #define SMB_ADS_USER_ACCT_CTL_PASSWD_EXPIRED 0x00800000 #define SMB_ADS_USER_ACCT_CTL_TRUSTED_TO_AUTH_FOR_DELEGATION 0x01000000 /* * Length of "dc=" prefix. */ #define SMB_ADS_DN_PREFIX_LEN 3 static char *smb_ads_computer_objcls[] = { "top", "person", "organizationalPerson", "user", "computer", NULL }; static char *smb_ads_share_objcls[] = { "top", "leaf", "connectionPoint", "volume", NULL }; /* Cached ADS server to communicate with */ static smb_ads_host_info_t *smb_ads_cached_host_info = NULL; static mutex_t smb_ads_cached_host_mtx; /* * SMB ADS config cache is maintained to facilitate the detection of * changes in configuration that is relevant to AD selection. */ typedef struct smb_ads_config { char c_site[SMB_ADS_SITE_MAX]; mutex_t c_mtx; } smb_ads_config_t; static smb_ads_config_t smb_ads_cfg; /* attribute/value pair */ typedef struct smb_ads_avpair { char *avp_attr; char *avp_val; } smb_ads_avpair_t; /* query status */ typedef enum smb_ads_qstat { SMB_ADS_STAT_ERR = -2, SMB_ADS_STAT_DUP, SMB_ADS_STAT_NOT_FOUND, SMB_ADS_STAT_FOUND } smb_ads_qstat_t; typedef struct smb_ads_host_list { int ah_cnt; smb_ads_host_info_t *ah_list; } smb_ads_host_list_t; static int smb_ads_open_main(smb_ads_handle_t **, char *, char *, char *); static int smb_ads_add_computer(smb_ads_handle_t *, int, char *); static int smb_ads_modify_computer(smb_ads_handle_t *, int, char *); static int smb_ads_computer_op(smb_ads_handle_t *, int, int, char *); static smb_ads_qstat_t smb_ads_lookup_computer_n_attr(smb_ads_handle_t *, smb_ads_avpair_t *, int, char *); static int smb_ads_update_computer_cntrl_attr(smb_ads_handle_t *, int, char *); static krb5_kvno smb_ads_lookup_computer_attr_kvno(smb_ads_handle_t *, char *); static void smb_ads_free_cached_host(void); static int smb_ads_alloc_attr(LDAPMod **, int); static void smb_ads_free_attr(LDAPMod **); static int smb_ads_get_dc_level(smb_ads_handle_t *); static smb_ads_qstat_t smb_ads_find_computer(smb_ads_handle_t *, char *); static smb_ads_qstat_t smb_ads_getattr(LDAP *, LDAPMessage *, smb_ads_avpair_t *); static smb_ads_qstat_t smb_ads_get_qstat(smb_ads_handle_t *, LDAPMessage *, smb_ads_avpair_t *); static boolean_t smb_ads_is_same_domain(char *, char *); static smb_ads_host_info_t *smb_ads_dup_host_info(smb_ads_host_info_t *); static char *smb_ads_get_sharedn(const char *, const char *, const char *); static krb5_enctype *smb_ads_get_enctypes(int, int *); /* * smb_ads_init * * Initializes the ADS config cache. */ void smb_ads_init(void) { (void) mutex_lock(&smb_ads_cfg.c_mtx); (void) smb_config_getstr(SMB_CI_ADS_SITE, smb_ads_cfg.c_site, SMB_ADS_SITE_MAX); (void) mutex_unlock(&smb_ads_cfg.c_mtx); /* Force -lads to load, for dtrace. */ DsFreeDcInfo(NULL); } void smb_ads_fini(void) { smb_ads_free_cached_host(); } /* * smb_ads_refresh * * This function will be called when smb/server SMF service is refreshed. * (See smbd_join.c) * * Clearing the smb_ads_cached_host_info would allow the next DC * discovery process to pick up an AD based on the new AD configuration. */ void smb_ads_refresh(boolean_t force_rediscovery) { char new_site[SMB_ADS_SITE_MAX]; (void) smb_config_getstr(SMB_CI_ADS_SITE, new_site, SMB_ADS_SITE_MAX); (void) mutex_lock(&smb_ads_cfg.c_mtx); (void) strlcpy(smb_ads_cfg.c_site, new_site, SMB_ADS_SITE_MAX); (void) mutex_unlock(&smb_ads_cfg.c_mtx); smb_ads_free_cached_host(); if (force_rediscovery) { (void) _DsForceRediscovery(NULL, 0); } } /* * smb_ads_build_unc_name * * Construct the UNC name of the share object in the format of * \\hostname.domain\shareUNC * * Returns 0 on success, -1 on error. */ int smb_ads_build_unc_name(char *unc_name, int maxlen, const char *hostname, const char *shareUNC) { char my_domain[MAXHOSTNAMELEN]; if (smb_getfqdomainname(my_domain, sizeof (my_domain)) != 0) return (-1); (void) snprintf(unc_name, maxlen, "\\\\%s.%s\\%s", hostname, my_domain, shareUNC); return (0); } /* * The cached ADS host is no longer valid if one of the following criteria * is satisfied: * * 1) not in the specified domain * 2) not the sought host (if specified) * 3) not reachable * * The caller is responsible for acquiring the smb_ads_cached_host_mtx lock * prior to calling this function. * * Return B_TRUE if the cache host is still valid. Otherwise, return B_FALSE. */ static boolean_t smb_ads_validate_cache_host(char *domain) { if (!smb_ads_cached_host_info) return (B_FALSE); if (!smb_ads_is_same_domain(smb_ads_cached_host_info->name, domain)) return (B_FALSE); return (B_TRUE); } /* * smb_ads_match_hosts_same_domain * * Returns true, if the cached ADS host is in the same domain as the * current (given) domain. */ static boolean_t smb_ads_is_same_domain(char *cached_host_name, char *current_domain) { char *cached_host_domain; if ((cached_host_name == NULL) || (current_domain == NULL)) return (B_FALSE); cached_host_domain = strchr(cached_host_name, '.'); if (cached_host_domain == NULL) return (B_FALSE); ++cached_host_domain; if (smb_strcasecmp(cached_host_domain, current_domain, 0)) return (B_FALSE); return (B_TRUE); } /* * smb_ads_dup_host_info * * Duplicates the passed smb_ads_host_info_t structure. * Caller must free memory allocated by this method. * * Returns a reference to the duplicated smb_ads_host_info_t structure. * Returns NULL on error. */ static smb_ads_host_info_t * smb_ads_dup_host_info(smb_ads_host_info_t *ads_host) { smb_ads_host_info_t *dup_host; if (ads_host == NULL) return (NULL); dup_host = malloc(sizeof (smb_ads_host_info_t)); if (dup_host != NULL) bcopy(ads_host, dup_host, sizeof (smb_ads_host_info_t)); return (dup_host); } /* * smb_ads_find_host * * Finds an ADS host in a given domain. * * If the cached host is valid, it will be used. Otherwise, a DC will * be selected based on the following criteria: * * 1) pdc (aka preferred DC) configuration * 2) AD site configuration - the scope of the DNS lookup will be * restricted to the specified site. * 3) DC on the same subnet * 4) DC with the lowest priority/highest weight * * The above items are listed in decreasing preference order. The selected * DC must be online. * * If this function is called during domain join, the specified kpasswd server * takes precedence over preferred DC, AD site, and so on. * * Parameters: * domain: fully-qualified domain name. * * Returns: * A copy of the cached host info is returned. The caller is responsible * for deallocating the memory returned by this function. */ /*ARGSUSED*/ smb_ads_host_info_t * smb_ads_find_host(char *domain) { smb_ads_host_info_t *host = NULL; DOMAIN_CONTROLLER_INFO *dci = NULL; struct sockaddr_storage *ss; uint32_t flags = DS_DS_FLAG; uint32_t status; int tries; (void) mutex_lock(&smb_ads_cached_host_mtx); if (smb_ads_validate_cache_host(domain)) { host = smb_ads_dup_host_info(smb_ads_cached_host_info); (void) mutex_unlock(&smb_ads_cached_host_mtx); return (host); } (void) mutex_unlock(&smb_ads_cached_host_mtx); smb_ads_free_cached_host(); /* * The _real_ DC Locator is over in idmapd. * Door call over there to get it. */ tries = 15; again: status = _DsGetDcName( NULL, /* ComputerName */ domain, NULL, /* DomainGuid */ NULL, /* SiteName */ flags, &dci); switch (status) { case 0: break; /* * We can see these errors when joining a domain, if we race * asking idmap for the DC before it knows the new domain. */ case NT_STATUS_NO_SUCH_DOMAIN: /* Specified domain unknown */ case NT_STATUS_INVALID_SERVER_STATE: /* not in domain mode. */ if (--tries > 0) { (void) sleep(1); goto again; } /* FALLTHROUGH */ case NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND: case NT_STATUS_CANT_WAIT: /* timeout over in idmap */ default: return (NULL); } host = calloc(1, sizeof (*host)); if (host == NULL) goto out; (void) strlcpy(host->name, dci->DomainControllerName, MAXHOSTNAMELEN); ss = (void *)dci->_sockaddr; switch (ss->ss_family) { case AF_INET: { struct sockaddr_in *sin = (void *)ss; host->port = ntohs(sin->sin_port); host->ipaddr.a_family = AF_INET; (void) memcpy(&host->ipaddr.a_ipv4, &sin->sin_addr, sizeof (in_addr_t)); break; } case AF_INET6: { struct sockaddr_in6 *sin6 = (void *)ss; host->port = ntohs(sin6->sin6_port); host->ipaddr.a_family = AF_INET6; (void) memcpy(&host->ipaddr.a_ipv6, &sin6->sin6_addr, sizeof (in6_addr_t)); break; } default: syslog(LOG_ERR, "no addr for DC %s", dci->DomainControllerName); free(host); host = NULL; goto out; } host->flags = dci->Flags; (void) mutex_lock(&smb_ads_cached_host_mtx); if (!smb_ads_cached_host_info) smb_ads_cached_host_info = smb_ads_dup_host_info(host); (void) mutex_unlock(&smb_ads_cached_host_mtx); out: DsFreeDcInfo(dci); return (host); } /* * Return the number of dots in a string. */ static int smb_ads_count_dots(const char *s) { int ndots = 0; while (*s) { if (*s++ == '.') ndots++; } return (ndots); } /* * Convert a domain name in dot notation to distinguished name format, * for example: sun.com -> dc=sun,dc=com. * * Returns a pointer to an allocated buffer containing the distinguished * name. */ static char * smb_ads_convert_domain(const char *domain_name) { const char *s; char *dn_name; char buf[2]; int ndots; int len; if (domain_name == NULL || *domain_name == 0) return (NULL); ndots = smb_ads_count_dots(domain_name); ++ndots; len = strlen(domain_name) + (ndots * SMB_ADS_DN_PREFIX_LEN) + 1; if ((dn_name = malloc(len)) == NULL) return (NULL); bzero(dn_name, len); (void) strlcpy(dn_name, "dc=", len); buf[1] = '\0'; s = domain_name; while (*s) { if (*s == '.') { (void) strlcat(dn_name, ",dc=", len); } else { buf[0] = *s; (void) strlcat(dn_name, buf, len); } ++s; } return (dn_name); } /* * smb_ads_free_cached_host * * Free the memory use by the global smb_ads_cached_host_info & set it to NULL. */ static void smb_ads_free_cached_host(void) { (void) mutex_lock(&smb_ads_cached_host_mtx); if (smb_ads_cached_host_info) { free(smb_ads_cached_host_info); smb_ads_cached_host_info = NULL; } (void) mutex_unlock(&smb_ads_cached_host_mtx); } /* * smb_ads_open * Open a LDAP connection to an ADS server if the system is in domain mode. * Acquire both Kerberos TGT and LDAP service tickets for the host principal. * * This function should only be called after the system is successfully joined * to a domain. */ smb_ads_handle_t * smb_ads_open(void) { char domain[MAXHOSTNAMELEN]; smb_ads_handle_t *h; smb_ads_status_t err; if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN) return (NULL); if (smb_getfqdomainname(domain, MAXHOSTNAMELEN) != 0) return (NULL); err = smb_ads_open_main(&h, domain, NULL, NULL); if (err != 0) { smb_ads_log_errmsg(err); return (NULL); } return (h); } static int smb_ads_saslcallback(LDAP *ld, unsigned flags, void *defaults, void *prompts) { NOTE(ARGUNUSED(ld, defaults)); sasl_interact_t *interact; if (prompts == NULL || flags != LDAP_SASL_INTERACTIVE) return (LDAP_PARAM_ERROR); /* There should be no extra arguemnts for SASL/GSSAPI authentication */ for (interact = prompts; interact->id != SASL_CB_LIST_END; interact++) { interact->result = NULL; interact->len = 0; } return (LDAP_SUCCESS); } /* * smb_ads_open_main * Open a LDAP connection to an ADS server. * If ADS is enabled and the administrative username, password, and * ADS domain are defined then query DNS to find an ADS server if this is the * very first call to this routine. After an ADS server is found then this * server will be used everytime this routine is called until the system is * rebooted or the ADS server becomes unavailable then an ADS server will * be queried again. After the connection is made then an ADS handle * is created to be returned. * * After the LDAP connection, the LDAP version will be set to 3 using * ldap_set_option(). * * The LDAP connection is bound before the ADS handle is returned. * Parameters: * domain - fully-qualified domain name * user - the user account for whom the Kerberos TGT ticket and ADS * service tickets are acquired. * password - password of the specified user * * Returns: * NULL : can't connect to ADS server or other errors * smb_ads_handle_t* : handle to ADS server */ static int smb_ads_open_main(smb_ads_handle_t **hp, char *domain, char *user, char *password) { smb_ads_handle_t *ah; LDAP *ld; int version = 3; smb_ads_host_info_t *ads_host = NULL; int err, rc; *hp = NULL; if (user != NULL) { err = smb_kinit(domain, user, password); if (err != 0) { syslog(LOG_ERR, "smbns: kinit failed"); return (err); } user = NULL; password = NULL; } ads_host = smb_ads_find_host(domain); if (ads_host == NULL) return (SMB_ADS_CANT_LOCATE_DC); ah = (smb_ads_handle_t *)malloc(sizeof (smb_ads_handle_t)); if (ah == NULL) { free(ads_host); return (ENOMEM); } (void) memset(ah, 0, sizeof (smb_ads_handle_t)); if ((ld = ldap_init(ads_host->name, ads_host->port)) == NULL) { syslog(LOG_ERR, "smbns: ldap_init failed"); smb_ads_free_cached_host(); free(ah); free(ads_host); return (SMB_ADS_LDAP_INIT); } if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version) != LDAP_SUCCESS) { smb_ads_free_cached_host(); free(ah); free(ads_host); (void) ldap_unbind(ld); return (SMB_ADS_LDAP_SETOPT); } (void) ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF); ah->ld = ld; ah->domain = strdup(domain); if (ah->domain == NULL) { smb_ads_close(ah); free(ads_host); return (SMB_ADS_LDAP_SETOPT); } /* * ah->domain is often used for generating service principal name. * Convert it to lower case for RFC 4120 section 6.2.1 conformance. */ (void) smb_strlwr(ah->domain); ah->domain_dn = smb_ads_convert_domain(domain); if (ah->domain_dn == NULL) { smb_ads_close(ah); free(ads_host); return (SMB_ADS_LDAP_SET_DOM); } ah->hostname = strdup(ads_host->name); if (ah->hostname == NULL) { smb_ads_close(ah); free(ads_host); return (ENOMEM); } (void) mutex_lock(&smb_ads_cfg.c_mtx); if (*smb_ads_cfg.c_site != '\0') { if ((ah->site = strdup(smb_ads_cfg.c_site)) == NULL) { smb_ads_close(ah); (void) mutex_unlock(&smb_ads_cfg.c_mtx); free(ads_host); return (ENOMEM); } } else { ah->site = NULL; } (void) mutex_unlock(&smb_ads_cfg.c_mtx); syslog(LOG_DEBUG, "smbns: smb_ads_open_main"); syslog(LOG_DEBUG, "smbns: domain: %s", ah->domain); syslog(LOG_DEBUG, "smbns: domain_dn: %s", ah->domain_dn); syslog(LOG_DEBUG, "smbns: ip_addr: %s", ah->ip_addr); syslog(LOG_DEBUG, "smbns: hostname: %s", ah->hostname); syslog(LOG_DEBUG, "smbns: site: %s", (ah->site != NULL) ? ah->site : ""); rc = ldap_sasl_interactive_bind_s(ah->ld, "", "GSSAPI", NULL, NULL, LDAP_SASL_INTERACTIVE, &smb_ads_saslcallback, NULL); if (rc != LDAP_SUCCESS) { syslog(LOG_ERR, "smbns: ldap_sasl_..._bind_s failed (%s)", ldap_err2string(rc)); smb_ads_close(ah); free(ads_host); return (SMB_ADS_LDAP_SASL_BIND); } syslog(LOG_DEBUG, "smbns: ldap_sasl_..._bind_s success"); free(ads_host); *hp = ah; return (SMB_ADS_SUCCESS); } /* * smb_ads_close * Close connection to ADS server and free memory allocated for ADS handle. * LDAP unbind is called here. * Parameters: * ah: handle to ADS server * Returns: * void */ void smb_ads_close(smb_ads_handle_t *ah) { if (ah == NULL) return; /* close and free connection resources */ if (ah->ld) (void) ldap_unbind(ah->ld); free(ah->domain); free(ah->domain_dn); free(ah->hostname); free(ah->site); free(ah); } /* * smb_ads_alloc_attr * * Since the attrs is a null-terminated array, all elements * in the array (except the last one) will point to allocated * memory. */ static int smb_ads_alloc_attr(LDAPMod *attrs[], int num) { int i; bzero(attrs, num * sizeof (LDAPMod *)); for (i = 0; i < (num - 1); i++) { attrs[i] = (LDAPMod *)malloc(sizeof (LDAPMod)); if (attrs[i] == NULL) { smb_ads_free_attr(attrs); return (-1); } } return (0); } /* * smb_ads_free_attr * Free memory allocated when publishing a share. * Parameters: * attrs: an array of LDAPMod pointers * Returns: * None */ static void smb_ads_free_attr(LDAPMod *attrs[]) { int i; for (i = 0; attrs[i]; i++) { free(attrs[i]); } } /* * Returns share DN in an allocated buffer. The format of the DN is * cn=,, * * If the domain DN is not included in the container parameter, * then it will be appended to create the share DN. * * The caller must free the allocated buffer. */ static char * smb_ads_get_sharedn(const char *sharename, const char *container, const char *domain_dn) { char *share_dn; int rc, offset, container_len, domain_len; boolean_t append_domain = B_TRUE; container_len = strlen(container); domain_len = strlen(domain_dn); if (container_len >= domain_len) { /* offset to last domain_len characters */ offset = container_len - domain_len; if (smb_strcasecmp(container + offset, domain_dn, domain_len) == 0) append_domain = B_FALSE; } if (append_domain) rc = asprintf(&share_dn, "cn=%s,%s,%s", sharename, container, domain_dn); else rc = asprintf(&share_dn, "cn=%s,%s", sharename, container); return ((rc == -1) ? NULL : share_dn); } /* * smb_ads_add_share * Call by smb_ads_publish_share to create share object in ADS. * This routine specifies the attributes of an ADS LDAP share object. The first * attribute and values define the type of ADS object, the share object. The * second attribute and value define the UNC of the share data for the share * object. The LDAP synchronous add command is used to add the object into ADS. * The container location to add the object needs to specified. * Parameters: * ah : handle to ADS server * adsShareName: name of share object to be created in ADS * shareUNC : share name on NetForce * adsContainer: location in ADS to create share object * * Returns: * -1 : error * 0 : success */ int smb_ads_add_share(smb_ads_handle_t *ah, const char *adsShareName, const char *unc_name, const char *adsContainer) { LDAPMod *attrs[SMB_ADS_SHARE_NUM_ATTR]; int j = 0; char *share_dn; int ret; char *unc_names[] = {(char *)unc_name, NULL}; if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer, ah->domain_dn)) == NULL) return (-1); if (smb_ads_alloc_attr(attrs, SMB_ADS_SHARE_NUM_ATTR) != 0) { free(share_dn); return (-1); } attrs[j]->mod_op = LDAP_MOD_ADD; attrs[j]->mod_type = "objectClass"; attrs[j]->mod_values = smb_ads_share_objcls; attrs[++j]->mod_op = LDAP_MOD_ADD; attrs[j]->mod_type = "uNCName"; attrs[j]->mod_values = unc_names; if ((ret = ldap_add_s(ah->ld, share_dn, attrs)) != LDAP_SUCCESS) { if (ret == LDAP_NO_SUCH_OBJECT) { syslog(LOG_ERR, "Failed to publish share %s in" \ " AD. Container does not exist: %s.\n", adsShareName, share_dn); } else { syslog(LOG_ERR, "Failed to publish share %s in" \ " AD: %s (%s).\n", adsShareName, share_dn, ldap_err2string(ret)); } smb_ads_free_attr(attrs); free(share_dn); return (ret); } free(share_dn); smb_ads_free_attr(attrs); return (0); } /* * smb_ads_del_share * Call by smb_ads_remove_share to remove share object from ADS. The container * location to remove the object needs to specified. The LDAP synchronous * delete command is used. * Parameters: * ah : handle to ADS server * adsShareName: name of share object in ADS to be removed * adsContainer: location of share object in ADS * Returns: * -1 : error * 0 : success */ static int smb_ads_del_share(smb_ads_handle_t *ah, const char *adsShareName, const char *adsContainer) { char *share_dn; int ret; if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer, ah->domain_dn)) == NULL) return (-1); if ((ret = ldap_delete_s(ah->ld, share_dn)) != LDAP_SUCCESS) { smb_tracef("ldap_delete: %s", ldap_err2string(ret)); free(share_dn); return (-1); } free(share_dn); return (0); } /* * smb_ads_escape_search_filter_chars * * This routine will escape the special characters found in a string * that will later be passed to the ldap search filter. * * RFC 1960 - A String Representation of LDAP Search Filters * 3. String Search Filter Definition * If a value must contain one of the characters '*' OR '(' OR ')', * these characters * should be escaped by preceding them with the backslash '\' character. * * RFC 2252 - LDAP Attribute Syntax Definitions * a backslash quoting mechanism is used to escape * the following separator symbol character (such as "'", "$" or "#") if * it should occur in that string. */ static int smb_ads_escape_search_filter_chars(const char *src, char *dst) { int avail = SMB_ADS_MAXBUFLEN - 1; /* reserve a space for NULL char */ if (src == NULL || dst == NULL) return (-1); while (*src) { if (!avail) { *dst = 0; return (-1); } switch (*src) { case '\\': case '\'': case '$': case '#': case '*': case '(': case ')': *dst++ = '\\'; avail--; /* fall through */ default: *dst++ = *src++; avail--; } } *dst = 0; return (0); } /* * smb_ads_lookup_share * The search filter is set to search for a specific share name in the * specified ADS container. The LDSAP synchronous search command is used. * Parameters: * ah : handle to ADS server * adsShareName: name of share object in ADS to be searched * adsContainer: location of share object in ADS * Returns: * -1 : error * 0 : not found * 1 : found */ int smb_ads_lookup_share(smb_ads_handle_t *ah, const char *adsShareName, const char *adsContainer, char *unc_name) { char *attrs[4], filter[SMB_ADS_MAXBUFLEN]; char *share_dn; int ret; LDAPMessage *res; char tmpbuf[SMB_ADS_MAXBUFLEN]; if (adsShareName == NULL || adsContainer == NULL) return (-1); if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer, ah->domain_dn)) == NULL) return (-1); res = NULL; attrs[0] = "cn"; attrs[1] = "objectClass"; attrs[2] = "uNCName"; attrs[3] = NULL; if (smb_ads_escape_search_filter_chars(unc_name, tmpbuf) != 0) { free(share_dn); return (-1); } (void) snprintf(filter, sizeof (filter), "(&(objectClass=volume)(uNCName=%s))", tmpbuf); if ((ret = ldap_search_s(ah->ld, share_dn, LDAP_SCOPE_BASE, filter, attrs, 0, &res)) != LDAP_SUCCESS) { if (ret != LDAP_NO_SUCH_OBJECT) smb_tracef("%s: ldap_search: %s", share_dn, ldap_err2string(ret)); (void) ldap_msgfree(res); free(share_dn); return (0); } (void) free(share_dn); /* no match is found */ if (ldap_count_entries(ah->ld, res) == 0) { (void) ldap_msgfree(res); return (0); } /* free the search results */ (void) ldap_msgfree(res); return (1); } /* * smb_ads_publish_share * Publish share into ADS. If a share name already exist in ADS in the same * container then the existing share object is removed before adding the new * share object. * Parameters: * ah : handle return from smb_ads_open * adsShareName: name of share to be added to ADS directory * shareUNC : name of share on client, can be NULL to use the same name * as adsShareName * adsContainer: location for share to be added in ADS directory, ie * ou=share_folder * uncType : use UNC_HOSTNAME to use hostname for UNC, use UNC_HOSTADDR * to use host ip addr for UNC. * Returns: * -1 : error * 0 : success */ int smb_ads_publish_share(smb_ads_handle_t *ah, const char *adsShareName, const char *shareUNC, const char *adsContainer, const char *hostname) { int ret; char unc_name[SMB_ADS_MAXBUFLEN]; if (adsShareName == NULL || adsContainer == NULL) return (-1); if (shareUNC == 0 || *shareUNC == 0) shareUNC = adsShareName; if (smb_ads_build_unc_name(unc_name, sizeof (unc_name), hostname, shareUNC) < 0) return (-1); ret = smb_ads_lookup_share(ah, adsShareName, adsContainer, unc_name); switch (ret) { case 1: (void) smb_ads_del_share(ah, adsShareName, adsContainer); ret = smb_ads_add_share(ah, adsShareName, unc_name, adsContainer); break; case 0: ret = smb_ads_add_share(ah, adsShareName, unc_name, adsContainer); if (ret == LDAP_ALREADY_EXISTS) ret = -1; break; case -1: default: /* return with error code */ ret = -1; } return (ret); } /* * smb_ads_remove_share * Remove share from ADS. A search is done first before explicitly removing * the share. * Parameters: * ah : handle return from smb_ads_open * adsShareName: name of share to be removed from ADS directory * adsContainer: location for share to be removed from ADS directory, ie * ou=share_folder * Returns: * -1 : error * 0 : success */ int smb_ads_remove_share(smb_ads_handle_t *ah, const char *adsShareName, const char *shareUNC, const char *adsContainer, const char *hostname) { int ret; char unc_name[SMB_ADS_MAXBUFLEN]; if (adsShareName == NULL || adsContainer == NULL) return (-1); if (shareUNC == 0 || *shareUNC == 0) shareUNC = adsShareName; if (smb_ads_build_unc_name(unc_name, sizeof (unc_name), hostname, shareUNC) < 0) return (-1); ret = smb_ads_lookup_share(ah, adsShareName, adsContainer, unc_name); if (ret == 0) return (0); if (ret == -1) return (-1); return (smb_ads_del_share(ah, adsShareName, adsContainer)); } /* * smb_ads_get_new_comp_dn * * Build the distinguished name for a new machine account * prepend: cn=SamAccountName, cn=Computers, ...domain_dn... */ static void smb_ads_get_new_comp_dn(smb_ads_handle_t *ah, char *buf, size_t buflen, char *container) { char nbname[NETBIOS_NAME_SZ]; if (container == NULL) container = "cn=" SMB_ADS_COMPUTERS_CN; (void) smb_getnetbiosname(nbname, sizeof (nbname)); (void) snprintf(buf, buflen, "cn=%s,%s,%s", nbname, container, ah->domain_dn); } /* * smb_ads_add_computer * * Returns 0 upon success. Otherwise, returns -1. */ static int smb_ads_add_computer(smb_ads_handle_t *ah, int dclevel, char *dn) { return (smb_ads_computer_op(ah, LDAP_MOD_ADD, dclevel, dn)); } /* * smb_ads_modify_computer * * Returns 0 upon success. Otherwise, returns -1. */ static int smb_ads_modify_computer(smb_ads_handle_t *ah, int dclevel, char *dn) { return (smb_ads_computer_op(ah, LDAP_MOD_REPLACE, dclevel, dn)); } /* * smb_ads_get_dc_level * * Returns the functional level of the DC upon success. * Otherwise, -1 is returned. */ static int smb_ads_get_dc_level(smb_ads_handle_t *ah) { LDAPMessage *res, *entry; char *attr[2]; char **vals; int rc; res = NULL; attr[0] = SMB_ADS_ATTR_DCLEVEL; attr[1] = NULL; rc = ldap_search_s(ah->ld, "", LDAP_SCOPE_BASE, NULL, attr, 0, &res); if (rc != LDAP_SUCCESS) { syslog(LOG_ERR, "smb_ads_get_dc_level: " "LDAP search, error %s", ldap_err2string(rc)); (void) ldap_msgfree(res); return (-1); } /* no match for the specified attribute is found */ if (ldap_count_entries(ah->ld, res) == 0) { (void) ldap_msgfree(res); return (-1); } rc = -1; entry = ldap_first_entry(ah->ld, res); if (entry) { if ((vals = ldap_get_values(ah->ld, entry, SMB_ADS_ATTR_DCLEVEL)) == NULL) { /* * Observed the values aren't populated * by the Windows 2000 server. */ syslog(LOG_DEBUG, "smb_ads_get_dc_level: " "LDAP values missing, assume W2K"); (void) ldap_msgfree(res); return (SMB_ADS_DCLEVEL_W2K); } if (vals[0] != NULL) { rc = atoi(vals[0]); syslog(LOG_DEBUG, "smb_ads_get_dc_level: " "LDAP value %d", rc); } ldap_value_free(vals); } (void) ldap_msgfree(res); return (rc); } /* * The fully-qualified hostname returned by this function is often used for * constructing service principal name. Return the fully-qualified hostname * in lower case for RFC 4120 section 6.2.1 conformance. */ static int smb_ads_getfqhostname(smb_ads_handle_t *ah, char *fqhost, int len) { if (smb_gethostname(fqhost, len, SMB_CASE_LOWER) != 0) return (-1); (void) strlcat(fqhost, ".", len); (void) strlcat(fqhost, ah->domain, len); return (0); } static int smb_ads_computer_op(smb_ads_handle_t *ah, int op, int dclevel, char *dn) { LDAPMod *attrs[SMB_ADS_COMPUTER_NUM_ATTR]; char *sam_val[2]; char *ctl_val[2], *fqh_val[2]; char *encrypt_val[2]; int j = -1; int ret, usrctl_flags = 0; char sam_acct[SMB_SAMACCT_MAXLEN]; char fqhost[MAXHOSTNAMELEN]; char usrctl_buf[16]; char encrypt_buf[16]; int max; smb_krb5_pn_set_t spn, upn; syslog(LOG_DEBUG, "smb_ads_computer_op, op=%s dn=%s", (op == LDAP_MOD_ADD) ? "add" : "replace", dn); if (smb_getsamaccount(sam_acct, sizeof (sam_acct)) != 0) return (-1); if (smb_ads_getfqhostname(ah, fqhost, MAXHOSTNAMELEN)) return (-1); /* The SPN attribute is multi-valued and must be 1 or greater */ if (smb_krb5_get_pn_set(&spn, SMB_PN_SPN_ATTR, ah->domain) == 0) return (-1); /* The UPN attribute is single-valued and cannot be zero */ if (smb_krb5_get_pn_set(&upn, SMB_PN_UPN_ATTR, ah->domain) != 1) { smb_krb5_free_pn_set(&spn); smb_krb5_free_pn_set(&upn); return (-1); } max = (SMB_ADS_COMPUTER_NUM_ATTR - ((op != LDAP_MOD_ADD) ? 1 : 0)) - (dclevel >= SMB_ADS_DCLEVEL_W2K8 ? 0 : 1); if (smb_ads_alloc_attr(attrs, max) != 0) { smb_krb5_free_pn_set(&spn); smb_krb5_free_pn_set(&upn); return (-1); } /* objectClass attribute is not modifiable. */ if (op == LDAP_MOD_ADD) { attrs[++j]->mod_op = op; attrs[j]->mod_type = "objectClass"; attrs[j]->mod_values = smb_ads_computer_objcls; } attrs[++j]->mod_op = op; attrs[j]->mod_type = SMB_ADS_ATTR_SAMACCT; sam_val[0] = sam_acct; sam_val[1] = 0; attrs[j]->mod_values = sam_val; attrs[++j]->mod_op = op; attrs[j]->mod_type = SMB_ADS_ATTR_UPN; attrs[j]->mod_values = upn.s_pns; attrs[++j]->mod_op = op; attrs[j]->mod_type = SMB_ADS_ATTR_SPN; attrs[j]->mod_values = spn.s_pns; attrs[++j]->mod_op = op; attrs[j]->mod_type = SMB_ADS_ATTR_CTL; usrctl_flags |= (SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT | SMB_ADS_USER_ACCT_CTL_PASSWD_NOTREQD | SMB_ADS_USER_ACCT_CTL_ACCOUNTDISABLE); (void) snprintf(usrctl_buf, sizeof (usrctl_buf), "%d", usrctl_flags); ctl_val[0] = usrctl_buf; ctl_val[1] = 0; attrs[j]->mod_values = ctl_val; attrs[++j]->mod_op = op; attrs[j]->mod_type = SMB_ADS_ATTR_DNSHOST; fqh_val[0] = fqhost; fqh_val[1] = 0; attrs[j]->mod_values = fqh_val; /* enctypes support starting in Windows Server 2008 */ if (dclevel > SMB_ADS_DCLEVEL_W2K3) { attrs[++j]->mod_op = op; attrs[j]->mod_type = SMB_ADS_ATTR_ENCTYPES; (void) snprintf(encrypt_buf, sizeof (encrypt_buf), "%d", SMB_ADS_ENC_AES256 + SMB_ADS_ENC_AES128 + SMB_ADS_ENC_RC4 + SMB_ADS_ENC_DES_MD5 + SMB_ADS_ENC_DES_CRC); encrypt_val[0] = encrypt_buf; encrypt_val[1] = 0; attrs[j]->mod_values = encrypt_val; } switch (op) { case LDAP_MOD_ADD: if ((ret = ldap_add_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) { syslog(LOG_NOTICE, "ldap_add: %s", ldap_err2string(ret)); ret = -1; } break; case LDAP_MOD_REPLACE: if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) { syslog(LOG_NOTICE, "ldap_modify: %s", ldap_err2string(ret)); ret = -1; } break; default: ret = -1; } smb_ads_free_attr(attrs); smb_krb5_free_pn_set(&spn); smb_krb5_free_pn_set(&upn); return (ret); } /* * Delete an ADS computer account. */ static void smb_ads_del_computer(smb_ads_handle_t *ah, char *dn) { int rc; if ((rc = ldap_delete_s(ah->ld, dn)) != LDAP_SUCCESS) smb_tracef("ldap_delete: %s", ldap_err2string(rc)); } /* * Gets the value of the given attribute. */ static smb_ads_qstat_t smb_ads_getattr(LDAP *ld, LDAPMessage *entry, smb_ads_avpair_t *avpair) { char **vals; smb_ads_qstat_t rc = SMB_ADS_STAT_FOUND; assert(avpair); avpair->avp_val = NULL; syslog(LOG_DEBUG, "smbns: ads_getattr (%s)", avpair->avp_attr); vals = ldap_get_values(ld, entry, avpair->avp_attr); if (!vals) { syslog(LOG_DEBUG, "smbns: ads_getattr err: no vals"); return (SMB_ADS_STAT_NOT_FOUND); } if (!vals[0]) { syslog(LOG_DEBUG, "smbns: ads_getattr err: no vals[0]"); ldap_value_free(vals); return (SMB_ADS_STAT_NOT_FOUND); } avpair->avp_val = strdup(vals[0]); if (!avpair->avp_val) { syslog(LOG_DEBUG, "smbns: ads_getattr err: no mem"); rc = SMB_ADS_STAT_ERR; } else { syslog(LOG_DEBUG, "smbns: ads_getattr (%s) OK, val=%s", avpair->avp_attr, avpair->avp_val); } ldap_value_free(vals); return (rc); } /* * Process query's result, making sure we have what we need. * * There's some non-obvious logic here for checking the returned * DNS name for the machine account, trying to avoid modifying * someone else's machine account. When we search for a machine * account we always ask for the DNS name. For a pre-created * machine account, the DNS name will be not set, and that's OK. * If we see a DNS name and it doesn't match our DNS name, we'll * assume the account belongs to someone else and return "DUP". * * Only do the DNS name check for our initial search for the * machine account, which has avpair->avp_attr = SMB_ADS_ATTR_DN */ static smb_ads_qstat_t smb_ads_get_qstat(smb_ads_handle_t *ah, LDAPMessage *res, smb_ads_avpair_t *avpair) { smb_ads_qstat_t rc = SMB_ADS_STAT_FOUND; LDAPMessage *entry; if (ldap_count_entries(ah->ld, res) == 0) { syslog(LOG_DEBUG, "smbns: find_computer, " "ldap_count_entries zero"); return (SMB_ADS_STAT_NOT_FOUND); } if ((entry = ldap_first_entry(ah->ld, res)) == NULL) { syslog(LOG_DEBUG, "smbns: find_computer, " "ldap_first_entry error"); return (SMB_ADS_STAT_ERR); } /* Have an LDAP entry (found something) */ syslog(LOG_DEBUG, "smbns: find_computer, have LDAP resp."); if (avpair != NULL && strcmp(avpair->avp_attr, SMB_ADS_ATTR_DN) == 0) { char fqhost[MAXHOSTNAMELEN]; smb_ads_avpair_t dnshost_avp; syslog(LOG_DEBUG, "smbns: find_computer, check DNS name"); if (smb_ads_getfqhostname(ah, fqhost, MAXHOSTNAMELEN)) return (SMB_ADS_STAT_ERR); dnshost_avp.avp_attr = SMB_ADS_ATTR_DNSHOST; dnshost_avp.avp_val = NULL; rc = smb_ads_getattr(ah->ld, entry, &dnshost_avp); /* * Status from finding the DNS name value */ switch (rc) { case SMB_ADS_STAT_FOUND: /* * Found a DNS name. If it doesn't match ours, * returns SMB_ADS_STAT_DUP to avoid overwriting * the computer account of another system whose * NetBIOS name collides with that of the current * system. */ if (strcasecmp(dnshost_avp.avp_val, fqhost)) { syslog(LOG_DEBUG, "smbns: find_computer, " "duplicate name (%s)", dnshost_avp.avp_val); rc = SMB_ADS_STAT_DUP; } free(dnshost_avp.avp_val); break; case SMB_ADS_STAT_NOT_FOUND: /* * No dNSHostname attribute, so probably a * pre-created computer account. Use it. * * Returns SMB_ADS_STAT_FOUND for the status * of finding the machine account. */ rc = SMB_ADS_STAT_FOUND; break; default: break; } if (rc != SMB_ADS_STAT_FOUND) return (rc); } if (avpair) { syslog(LOG_DEBUG, "smbns: find_computer, check %s", avpair->avp_attr); rc = smb_ads_getattr(ah->ld, entry, avpair); } return (rc); } /* * smb_ads_lookup_computer_n_attr * * If avpair is NULL, checks the status of the specified computer account. * Otherwise, looks up the value of the specified computer account's attribute. * If found, the value field of the avpair will be allocated and set. The * caller should free the allocated buffer. Caller avpair requests are: * smb_ads_find_computer() asks for SMB_ADS_ATTR_DN * smb_ads_lookup_computer_attr_kvno() SMB_ADS_ATTR_KVNO * * Return: * SMB_ADS_STAT_FOUND - if both the computer and the specified attribute is * found. * SMB_ADS_STAT_NOT_FOUND - if either the computer or the specified attribute * is not found. * SMB_ADS_STAT_DUP - if the computer account is already used by other systems * in the AD. This could happen if the hostname of multiple * systems resolved to the same NetBIOS name. * SMB_ADS_STAT_ERR - any failure. */ static smb_ads_qstat_t smb_ads_lookup_computer_n_attr(smb_ads_handle_t *ah, smb_ads_avpair_t *avpair, int scope, char *dn) { char *attrs[3], filter[SMB_ADS_MAXBUFLEN]; LDAPMessage *res; char sam_acct[SMB_SAMACCT_MAXLEN]; char tmpbuf[SMB_ADS_MAXBUFLEN]; smb_ads_qstat_t rc; int err; if (smb_getsamaccount(sam_acct, sizeof (sam_acct)) != 0) return (SMB_ADS_STAT_ERR); res = NULL; attrs[0] = SMB_ADS_ATTR_DNSHOST; attrs[1] = NULL; attrs[2] = NULL; if (avpair) { if (!avpair->avp_attr) return (SMB_ADS_STAT_ERR); attrs[1] = avpair->avp_attr; } if (smb_ads_escape_search_filter_chars(sam_acct, tmpbuf) != 0) return (SMB_ADS_STAT_ERR); (void) snprintf(filter, sizeof (filter), "(&(objectClass=computer)(%s=%s))", SMB_ADS_ATTR_SAMACCT, tmpbuf); syslog(LOG_DEBUG, "smbns: lookup_computer, " "dn=%s, scope=%d", dn, scope); syslog(LOG_DEBUG, "smbns: lookup_computer, " "filter=%s", filter); syslog(LOG_DEBUG, "smbns: lookup_computer, " "attrs[0]=%s", attrs[0]); syslog(LOG_DEBUG, "smbns: lookup_computer, " "attrs[1]=%s", attrs[1] ? attrs[1] : ""); err = ldap_search_s(ah->ld, dn, scope, filter, attrs, 0, &res); if (err != LDAP_SUCCESS) { syslog(LOG_DEBUG, "smbns: lookup_computer, " "LDAP search failed, dn=(%s), scope=%d, err=%s", dn, scope, ldap_err2string(err)); (void) ldap_msgfree(res); return (SMB_ADS_STAT_NOT_FOUND); } syslog(LOG_DEBUG, "smbns: find_computer, ldap_search OK"); rc = smb_ads_get_qstat(ah, res, avpair); if (rc == SMB_ADS_STAT_FOUND) { syslog(LOG_DEBUG, "smbns: find_computer, attr %s = %s", avpair->avp_attr, avpair->avp_val); } else { syslog(LOG_DEBUG, "smbns: find_computer, " "get query status, error %d", rc); } /* free the search results */ (void) ldap_msgfree(res); return (rc); } /* * smb_ads_find_computer * * Searches the directory for the machine account (SamAccountName) * If found, 'dn' will be set to the distinguished name of the system's * AD computer object. */ static smb_ads_qstat_t smb_ads_find_computer(smb_ads_handle_t *ah, char *dn) { smb_ads_qstat_t stat; smb_ads_avpair_t avpair; avpair.avp_attr = SMB_ADS_ATTR_DN; avpair.avp_val = NULL; (void) strlcpy(dn, ah->domain_dn, SMB_ADS_DN_MAX); stat = smb_ads_lookup_computer_n_attr(ah, &avpair, LDAP_SCOPE_SUBTREE, dn); if (stat == SMB_ADS_STAT_FOUND) { (void) strlcpy(dn, avpair.avp_val, SMB_ADS_DN_MAX); free(avpair.avp_val); } return (stat); } /* * Set a machine account password using LDAP * * Compose the funky unicode password string per * [MS-ADTS] 3.1.1.3.1.5.1 unicodePwd * which includes literal double quotes. */ static int smb_ads_update_acct_passwd(smb_ads_handle_t *ah, char *passwd, char *dn) { LDAPMod *attrs[2]; struct berval *bvp[2]; struct berval bval; char *qpass_buf = NULL; /* quoted password */ int qpass_len = 0; smb_wchar_t *ucpwd_buf = NULL; /* unicode pw */ int ucpwd_len = 0; /* length in bytes */ int ret = 0; if (smb_ads_alloc_attr(attrs, sizeof (attrs) / sizeof (LDAPMod *)) != 0) return (LDAP_NO_MEMORY); qpass_len = asprintf(&qpass_buf, "\"%s\"", passwd); if (qpass_len < 0) { qpass_len = 0; ret = LDAP_NO_MEMORY; goto out; } /* * Allocate and fill Unicode passwd buffer. * Size to include a smb_wchar_t null termination. */ ucpwd_len = smb_wcequiv_strlen(qpass_buf); ucpwd_buf = calloc(1, ucpwd_len + sizeof (smb_wchar_t)); if (ucpwd_buf == NULL) { ret = LDAP_NO_MEMORY; goto out; } ret = smb_mbstowcs(ucpwd_buf, qpass_buf, ucpwd_len / 2); if (ret < 0) { ret = LDAP_ENCODING_ERROR; goto out; } bval.bv_val = (char *)ucpwd_buf; bval.bv_len = ucpwd_len; bvp[0] = &bval; bvp[1] = NULL; attrs[0]->mod_op = LDAP_MOD_REPLACE | LDAP_MOD_BVALUES; attrs[0]->mod_type = SMB_ADS_ATTR_UCPWD; attrs[0]->mod_bvalues = bvp; if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) { syslog(LOG_NOTICE, "ldap_modify: %s", ldap_err2string(ret)); } out: freezero(ucpwd_buf, ucpwd_len); freezero(qpass_buf, qpass_len); smb_ads_free_attr(attrs); return (ret); } /* * smb_ads_update_computer_cntrl_attr * * Modify the user account control attribute of an existing computer * object on AD. * * Returns LDAP error code. */ static int smb_ads_update_computer_cntrl_attr(smb_ads_handle_t *ah, int flags, char *dn) { LDAPMod *attrs[2]; char *ctl_val[2]; int ret = 0; char usrctl_buf[16]; if (smb_ads_alloc_attr(attrs, sizeof (attrs) / sizeof (LDAPMod *)) != 0) return (LDAP_NO_MEMORY); attrs[0]->mod_op = LDAP_MOD_REPLACE; attrs[0]->mod_type = SMB_ADS_ATTR_CTL; (void) snprintf(usrctl_buf, sizeof (usrctl_buf), "%d", flags); ctl_val[0] = usrctl_buf; ctl_val[1] = 0; attrs[0]->mod_values = ctl_val; if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) { syslog(LOG_NOTICE, "ldap_modify: %s", ldap_err2string(ret)); } smb_ads_free_attr(attrs); return (ret); } /* * smb_ads_lookup_computer_attr_kvno * * Lookup the value of the Kerberos version number attribute of the computer * account. */ static krb5_kvno smb_ads_lookup_computer_attr_kvno(smb_ads_handle_t *ah, char *dn) { smb_ads_avpair_t avpair; int kvno = 1; avpair.avp_attr = SMB_ADS_ATTR_KVNO; avpair.avp_val = NULL; if (smb_ads_lookup_computer_n_attr(ah, &avpair, LDAP_SCOPE_BASE, dn) == SMB_ADS_STAT_FOUND) { kvno = atoi(avpair.avp_val); free(avpair.avp_val); } return (kvno); } /* Available as a work-around if smb_ads_update_acct_passwd fails */ boolean_t smbns_ads_try_kpasswd = 0; /* * smb_ads_join * * Besides the NT-4 style domain join (using MS-RPC), CIFS server also * provides the domain join using Kerberos Authentication, Keberos * Change & Set password, and LDAP protocols. Basically, AD join * operation would require the following tickets to be acquired for the * the user account that is provided for the domain join. * * 1) a Keberos TGT ticket, * 2) a ldap service ticket, and * 3) kadmin/changpw service ticket * * The ADS client first sends a ldap search request to find out whether * or not the workstation trust account already exists in the Active Directory. * The existing computer object for this workstation will be removed and * a new one will be added. The machine account password is randomly * generated and set for the newly created computer object using KPASSWD * protocol (See RFC 3244). Once the password is set, our ADS client * finalizes the machine account by modifying the user acount control * attribute of the computer object. Kerberos keys derived from the machine * account password will be stored locally in /etc/krb5/krb5.keytab file. * That would be needed while acquiring Kerberos TGT ticket for the host * principal after the domain join operation. */ smb_ads_status_t smb_ads_join(char *domain, char *container, char *user, char *usr_passwd, char *machine_passwd) { smb_ads_handle_t *ah = NULL; krb5_context ctx = NULL; krb5_principal *krb5princs = NULL; krb5_kvno kvno; boolean_t delete = B_TRUE; smb_ads_status_t rc; boolean_t new_acct; int dclevel, num, usrctl_flags = 0; smb_ads_qstat_t qstat; char dn[SMB_ADS_DN_MAX]; char tmpfile[] = SMBNS_KRB5_KEYTAB_TMP; int cnt, x; smb_krb5_pn_set_t spns; krb5_enctype *encptr; rc = smb_ads_open_main(&ah, domain, user, usr_passwd); if (rc != 0) { const char *s = smb_ads_strerror(rc); syslog(LOG_ERR, "smb_ads_join: open_main, error %s", s); smb_ccache_remove(SMB_CCACHE_PATH); return (rc); } if ((dclevel = smb_ads_get_dc_level(ah)) == -1) { smb_ads_close(ah); smb_ccache_remove(SMB_CCACHE_PATH); return (SMB_ADJOIN_ERR_GET_DCLEVEL); } qstat = smb_ads_find_computer(ah, dn); switch (qstat) { case SMB_ADS_STAT_FOUND: new_acct = B_FALSE; syslog(LOG_INFO, "smb_ads_join: machine account found." " Updating: %s", dn); if (smb_ads_modify_computer(ah, dclevel, dn) != 0) { smb_ads_close(ah); smb_ccache_remove(SMB_CCACHE_PATH); return (SMB_ADJOIN_ERR_MOD_TRUST_ACCT); } break; case SMB_ADS_STAT_NOT_FOUND: new_acct = B_TRUE; smb_ads_get_new_comp_dn(ah, dn, SMB_ADS_DN_MAX, container); syslog(LOG_INFO, "smb_ads_join: machine account not found." " Creating: %s", dn); if (smb_ads_add_computer(ah, dclevel, dn) != 0) { smb_ads_close(ah); smb_ccache_remove(SMB_CCACHE_PATH); return (SMB_ADJOIN_ERR_ADD_TRUST_ACCT); } break; default: syslog(LOG_INFO, "smb_ads_find_computer, rc=%d", qstat); if (qstat == SMB_ADS_STAT_DUP) rc = SMB_ADJOIN_ERR_DUP_TRUST_ACCT; else rc = SMB_ADJOIN_ERR_TRUST_ACCT; smb_ads_close(ah); smb_ccache_remove(SMB_CCACHE_PATH); return (rc); } if (smb_krb5_ctx_init(&ctx) != 0) { rc = SMB_ADJOIN_ERR_INIT_KRB_CTX; goto adjoin_cleanup; } if (smb_krb5_get_pn_set(&spns, SMB_PN_KEYTAB_ENTRY, ah->domain) == 0) { rc = SMB_ADJOIN_ERR_GET_SPNS; goto adjoin_cleanup; } if (smb_krb5_get_kprincs(ctx, spns.s_pns, spns.s_cnt, &krb5princs) != 0) { smb_krb5_free_pn_set(&spns); rc = SMB_ADJOIN_ERR_GET_SPNS; goto adjoin_cleanup; } cnt = spns.s_cnt; smb_krb5_free_pn_set(&spns); /* New machine_passwd was filled in by our caller. */ x = smb_ads_update_acct_passwd(ah, machine_passwd, dn); if (x != 0 && smbns_ads_try_kpasswd) x = smb_krb5_setpwd(ctx, ah->domain, machine_passwd); if (x != 0) { rc = SMB_ADJOIN_ERR_KSETPWD; goto adjoin_cleanup; } kvno = smb_ads_lookup_computer_attr_kvno(ah, dn); /* * Only members of Domain Admins and Enterprise Admins can set * the TRUSTED_FOR_DELEGATION userAccountControl flag. * Try to set this, but don't fail the join if we can't. * Look into just removing this... */ usrctl_flags = ( SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT | SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION | SMB_ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD); set_ctl_again: x = smb_ads_update_computer_cntrl_attr(ah, usrctl_flags, dn); if (x != LDAP_SUCCESS && (usrctl_flags & SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION) != 0) { syslog(LOG_NOTICE, "Unable to set the " "TRUSTED_FOR_DELEGATION userAccountControl flag on the " "machine account in Active Directory. It may be necessary " "to set that via Active Directory administration."); usrctl_flags &= ~SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION; goto set_ctl_again; } if (x != LDAP_SUCCESS) { rc = SMB_ADJOIN_ERR_UPDATE_CNTRL_ATTR; goto adjoin_cleanup; } if (mktemp(tmpfile) == NULL) { rc = SMB_ADJOIN_ERR_WRITE_KEYTAB; goto adjoin_cleanup; } encptr = smb_ads_get_enctypes(dclevel, &num); if (smb_krb5_kt_populate(ctx, ah->domain, krb5princs, cnt, tmpfile, kvno, machine_passwd, encptr, num) != 0) { rc = SMB_ADJOIN_ERR_WRITE_KEYTAB; goto adjoin_cleanup; } delete = B_FALSE; rc = SMB_ADS_SUCCESS; adjoin_cleanup: if (new_acct && delete) smb_ads_del_computer(ah, dn); if (rc != SMB_ADJOIN_ERR_INIT_KRB_CTX) { if (rc != SMB_ADJOIN_ERR_GET_SPNS) smb_krb5_free_kprincs(ctx, krb5princs, cnt); smb_krb5_ctx_fini(ctx); } /* commit keytab file */ if (rc == SMB_ADS_SUCCESS) { if (rename(tmpfile, SMBNS_KRB5_KEYTAB) != 0) { (void) unlink(tmpfile); rc = SMB_ADJOIN_ERR_COMMIT_KEYTAB; } } else { (void) unlink(tmpfile); } smb_ads_close(ah); smb_ccache_remove(SMB_CCACHE_PATH); return (rc); } struct xlate_table { int err; const char * const msg; }; static const struct xlate_table adjoin_table[] = { { SMB_ADS_SUCCESS, "Success" }, { SMB_ADS_KRB5_INIT_CTX, "Failed creating a Kerberos context." }, { SMB_ADS_KRB5_CC_DEFAULT, "Failed to resolve default credential cache." }, { SMB_ADS_KRB5_PARSE_PRINCIPAL, "Failed parsing the user principal name." }, { SMB_ADS_KRB5_GET_INIT_CREDS_OTHER, "Failed getting initial credentials. (See svc. log)" }, { SMB_ADS_KRB5_GET_INIT_CREDS_PW, "Failed getting initial credentials. (Wrong password?)" }, { SMB_ADS_KRB5_GET_INIT_CREDS_SKEW, "Failed getting initial credentials. (Clock skew too great)" }, { SMB_ADS_KRB5_CC_INITIALIZE, "Failed initializing the credential cache." }, { SMB_ADS_KRB5_CC_STORE_CRED, "Failed to update the credential cache." }, { SMB_ADS_CANT_LOCATE_DC, "Failed to locate a domain controller." }, { SMB_ADS_LDAP_INIT, "Failed to create an LDAP handle." }, { SMB_ADS_LDAP_SETOPT, "Failed to set an LDAP option." }, { SMB_ADS_LDAP_SET_DOM, "Failed to set the LDAP handle DN." }, { SMB_ADS_LDAP_SASL_BIND, "Failed to bind the LDAP handle. " "Usually indicates an authentication problem." }, { SMB_ADJOIN_ERR_GEN_PWD, "Failed to generate machine password." }, { SMB_ADJOIN_ERR_GET_DCLEVEL, "Unknown functional level of " "the domain controller. The rootDSE attribute named " "\"domainControllerFunctionality\" is missing from the " "Active Directory." }, { SMB_ADJOIN_ERR_ADD_TRUST_ACCT, "Failed to create the " "workstation trust account." }, { SMB_ADJOIN_ERR_MOD_TRUST_ACCT, "Failed to modify the " "workstation trust account." }, { SMB_ADJOIN_ERR_DUP_TRUST_ACCT, "Failed to create the " "workstation trust account because its name is already " "in use." }, { SMB_ADJOIN_ERR_TRUST_ACCT, "Error in querying the " "workstation trust account" }, { SMB_ADJOIN_ERR_INIT_KRB_CTX, "Failed to initialize Kerberos " "context." }, { SMB_ADJOIN_ERR_GET_SPNS, "Failed to get Kerberos " "principals." }, { SMB_ADJOIN_ERR_KSETPWD, "Failed to set machine password." }, { SMB_ADJOIN_ERR_UPDATE_CNTRL_ATTR, "Failed to modify " "userAccountControl attribute of the workstation trust " "account." }, { SMB_ADJOIN_ERR_WRITE_KEYTAB, "Error in writing to local " "keytab file (i.e /etc/krb5/krb5.keytab)." }, { SMB_ADJOIN_ERR_IDMAP_SET_DOMAIN, "Failed to update idmap " "configuration." }, { SMB_ADJOIN_ERR_IDMAP_REFRESH, "Failed to refresh idmap " "service." }, { SMB_ADJOIN_ERR_COMMIT_KEYTAB, "Failed to commit changes to " "local keytab file (i.e. /etc/krb5/krb5.keytab)." }, { SMB_ADJOIN_ERR_AUTH_NETLOGON, "Failed to authenticate using the new computer account." }, { SMB_ADJOIN_ERR_STORE_PROPS, "Failed to store computer account information locally." }, { SMB_ADJOIN_ERR_ALREADY_JOINED, "Already joined to a domain. " "Run \"smbadm join -w workgroup\" first." }, { 0, NULL } }; /* * smb_ads_strerror * * Lookup an error message for the specific adjoin error code. */ const char * smb_ads_strerror(int err) { const struct xlate_table *xt; if (err > 0 && err < SMB_ADS_ERRNO_GAP) return (strerror(err)); for (xt = adjoin_table; xt->msg; xt++) if (xt->err == err) return (xt->msg); return ("Unknown error code."); } void smb_ads_log_errmsg(smb_ads_status_t err) { const char *s = smb_ads_strerror(err); syslog(LOG_NOTICE, "%s", s); } /* * smb_ads_lookup_msdcs * * If server argument is set, try to locate the specified DC. * If it is set to empty string, locate any DCs in the specified domain. * Returns the discovered DC via buf. * * fqdn - fully-qualified domain name * dci - the name and address of the found DC */ uint32_t smb_ads_lookup_msdcs(char *fqdn, smb_dcinfo_t *dci) { smb_ads_host_info_t *hinfo = NULL; char ipstr[INET6_ADDRSTRLEN]; if (!fqdn || !dci) return (NT_STATUS_INTERNAL_ERROR); ipstr[0] = '\0'; if ((hinfo = smb_ads_find_host(fqdn)) == NULL) return (NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND); (void) smb_inet_ntop(&hinfo->ipaddr, ipstr, SMB_IPSTRLEN(hinfo->ipaddr.a_family)); smb_tracef("msdcsLookupADS: %s [%s]", hinfo->name, ipstr); (void) strlcpy(dci->dc_name, hinfo->name, sizeof (dci->dc_name)); dci->dc_addr = hinfo->ipaddr; dci->dc_flags = hinfo->flags; free(hinfo); return (NT_STATUS_SUCCESS); } static krb5_enctype * smb_ads_get_enctypes(int dclevel, int *num) { krb5_enctype *encptr; if (dclevel >= SMB_ADS_DCLEVEL_W2K8) { *num = sizeof (w2k8enctypes) / sizeof (krb5_enctype); encptr = w2k8enctypes; } else { *num = sizeof (pre_w2k8enctypes) / sizeof (krb5_enctype); encptr = pre_w2k8enctypes; } return (encptr); } /* * 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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * ntdomain_info * Temporary. It should be removed once NBTD is integrated. */ smb_ntdomain_t ntdomain_info; mutex_t ntdomain_mtx; cond_t ntdomain_cv; #define SMB_SERVER_SIGNATURE 0xaa550415 typedef struct smb_hostinfo { list_node_t hi_lnd; smb_nic_t hi_nic; char hi_nbname[NETBIOS_NAME_SZ]; name_entry_t hi_netname; uint32_t hi_nextannouce; int hi_reps; int hi_interval; uint8_t hi_updatecnt; uint32_t hi_type; smb_version_t hi_version; } smb_hostinfo_t; typedef struct smb_browserinfo { list_t bi_hlist; int bi_hcnt; rwlock_t bi_hlist_rwl; boolean_t bi_changed; mutex_t bi_mtx; } smb_browserinfo_t; static smb_browserinfo_t smb_binfo; static int smb_browser_init(void); static void smb_browser_infoinit(void); static void smb_browser_infoterm(void); static void smb_browser_infofree(void); void smb_browser_reconfig(void) { (void) mutex_lock(&smb_binfo.bi_mtx); smb_binfo.bi_changed = B_TRUE; (void) mutex_unlock(&smb_binfo.bi_mtx); } /* * 3. Browser Overview * * Hosts involved in the browsing process can be separated into two * distinct groups, browser clients and browser servers (often referred to * simply as "browsers"). * * A browser is a server which maintains information about servers - * primarily the domain they are in and the services that they are running * -- and about domains. Browsers may assume several different roles in * their lifetimes, and dynamically switch between them. * * Browser clients are of two types: workstations and (non-browser) * servers. In the context of browsing, workstations query browsers for the * information they contain; servers supply browsers the information by * registering with them. Note that, at times, browsers may themselves * behave as browser clients and query other browsers. * * For the purposes of this specification, a domain is simply a name with * which to associate a group of resources such as computers, servers and * users. Domains allow a convenient means for browser clients to restrict * the scope of a search when they query browser servers. Every domain has * a "master" server called the Primary Domain Controller (PDC) that * manages various activities within the domain. * * One browser for each domain on a subnet is designated the Local Master * Browser for that domain. Servers in its domain on the subnet register * with it, as do the Local Master Browsers for other domains on the * subnet. It uses these registrations to maintain authoritative * information about its domain on its subnet. If there are other subnets * in the network, it also knows the name of the server running the * domain's Domain Master Browser; it registers with it, and uses it to * obtain information about the rest of the network (see below). * * Clients on a subnet query browsers designated as the Backup Browsers for * the subnet (not the Master Browser). Backup Browsers maintain a copy of * the information on the Local Master Browser; they get it by periodically * querying the Local Master Browser for all of its information. Clients * find the Backup Browsers by asking the Local Master Browser. Clients are * expected to spread their queries evenly across Backup Browsers to * balance the load. * * The Local Master Browser is dynamically elected automatically. Multiple * Backup Browser Servers may exist per subnet; they are selected from * among the potential browser servers by the Local Master Browser, which * is configured to select enough to handle the expected query load. * * When there are multiple subnets, a Domain Master Browser is assigned * the task of keeping the multiple subnets in synchronization. The Primary * Domain Controller (PDC) always acts as the Domain Master Browser. The * Domain Master Browser periodically acts as a client and queries all the * Local Master Browsers for its domain, asking them for a list containing * all the domains and all the servers in their domain known within their * subnets; it merges all the replies into a single master list. This * allows a Domain Master Browser server to act as a collection point for * inter-subnet browsing information. Local Master Browsers periodically * query the Domain Master Browser to retrieve the network-wide information * it maintains. * * When a domain spans only a single subnet, there will not be any distinct * Local Master Browser; this role will be handled by the Domain Master * Browser. Similarly, the Domain Master Browser is always the Local Master * Browser for the subnet it is on. * * When a browser client suspects that the Local Master Browser has failed, * the client will instigate an election in which the browser servers * participate, and some browser servers may change roles. * * Some characteristics of a good browsing mechanism include: * . minimal network traffic * . minimum server discovery time * . minimum change discovery latency * . immunity to machine failures * * Historically, Browser implementations had been very closely tied to * NETBIOS and datagrams. The early implementations caused a lot of * broadcast traffic. See Appendix D for an overview that presents how the * Browser specification evolved. * * 4. Browsing Protocol Architecture * * This section first describes the how the browsing protocol is layered, * then describes the roles of clients, servers, and browsers in the * browsing subsystem. * * 4.1 Layering of Browsing Protocol Requests * * Most of the browser functionality is implemented using mailslots. * Mailslots provide a mechanism for fast, unreliable unidirectional data * transfer; they are named via ASCII "mailslot (path) name". Mailslots are * implemented using the CIFS Transact SMB which is encapsulated in a * NETBIOS datagram. Browser protocol requests are sent to browser specific * mailslots using some browser-specific NETBIOS names. These datagrams can * either be unicast or broadcast, depending on whether the NETBIOS name is * a "unique name" or a "group name". Various data structures, which are * detailed subsequently within this document, flow as the data portion of * the Transact SMB. * * Here is an example of a generic browser SMB, showing how a browser * request is encapsulated in a TRANSACT SMB request. Note that the PID, * TID, MID, UID, and Flags are all 0 in mailslot requests. * * SMB: C transact, File = \MAILSLOT\BROWSE * SMB: SMB Status = Error Success * SMB: Error class = No Error * SMB: Error code = No Error * SMB: Header: PID = 0x0000 TID = 0x0000 MID = 0x0000 UID = 0x0000 * SMB: Tree ID (TID) = 0 (0x0) * SMB: Process ID (PID) = 0 (0x0) * SMB: User ID (UID) = 0 (0x0) * SMB: Multiplex ID (MID) = 0 (0x0) * SMB: Flags Summary = 0 (0x0) * SMB: Command = C transact * SMB: Word count = 17 * SMB: Word parameters * SMB: Total parm bytes = 0 * SMB: Total data bytes = 33 * SMB: Max parm bytes = 0 * SMB: Max data bytes = 0 * SMB: Max setup words = 0 * SMB: Transact Flags Summary = 0 (0x0) * SMB: ...............0 = Leave session intact * SMB: ..............0. = Response required * SMB: Transact timeout = 0 (0x0) * SMB: Parameter bytes = 0 (0x0) * SMB: Parameter offset = 0 (0x0) * SMB: Data bytes = 33 (0x21) * SMB: Data offset = 86 (0x56) * SMB: Setup word count = 3 * SMB: Setup words * SMB: Mailslot opcode = Write mailslot * SMB: Transaction priority = 1 * SMB: Mailslot class = Unreliable (broadcast) * SMB: Byte count = 50 * SMB: Byte parameters * SMB: Path name = \MAILSLOT\BROWSE * SMB: Transaction data * SMB: Data: Number of data bytes remaining = 33 (0x0021) * * Note the SMB command is Transact, the opcode within the Transact SMB is * Mailslot Write, and the browser data structure is carried as the * Transact data. * The Transaction data begins with an opcode, that signifies the operation * and determines the size and structure of data that follows. This opcode * is named as per one of the below: * * HostAnnouncement 1 * AnnouncementRequest 2 * RequestElection 8 * GetBackupListReq 9 * GetBackupListResp 10 * BecomeBackup 11 * DomainAnnouncment 12 * MasterAnnouncement 13 * LocalMasterAnnouncement 15 * * Browser datagrams are often referred to as simply browser frames. The * frames are in particular, referred to by the name of the opcode within * the Transaction data e.g. a GetBackupListReq browser frame, a * RequestElection browser frame, etc. * * The structures that are sent as the data portion of the Transact SMB are * described in section(s) 6.2 through 6.12 in this document. These * structures are tightly packed, i.e. there are no intervening pad bytes * in the structure, unless they are explicitly described as being there. * All quantities are sent in native Intel format and multi-byte values are * transmitted least significant byte first. * * Besides mailslots and Transaction SMBs, the other important piece of the * browser architecture is the NetServerEnum2 request. This request that * allows an application to interrogate a Browser Server and obtain a * complete list of resources (servers, domains, etc) known to that Browser * server. Details of the NetServerEnum2 request are presented in section * 6.4. Some examples of the NetServerEnum2 request being used are when a * Local Master Browser sends a NetServerEnum2 request to the Domain Master * Browser and vice versa. Another example is when a browser client sends a * NetServerEnum2 request to a Backup Browser server. * * 4.3 Non-Browser Server * * A non-browser server is a server that has some resource(s) or service(s) * it wishes to advertise as being available using the browsing protocol. * Examples of non-browser servers would be an SQL server, print server, * etc. * * A non-browser server MUST periodically send a HostAnnouncement browser * frame, specifying the type of resources or services it is advertising. * Details are in section 6.5. * * A non-browser server SHOULD announce itself relatively frequently when * it first starts up in order to make its presence quickly known to the * browsers and thence to potential clients. The frequency of the * announcements SHOULD then be gradually stretched, so as to minimize * network traffic. Typically, non-browser servers announce themselves * once every minute upon start up and then gradually adjust the frequency * of the announcements to once every 12 minutes. * * A non-browser server SHOULD send a HostAnnouncement browser frame * specifying a type of 0 just prior to shutting down, to allow it to * quickly be removed from the list of available servers. * * A non-browser server MUST receive and process AnnouncementRequest frames * from the Local Master Browser, and MUST respond with a HostAnnouncement * frame, after a delay chosen randomly from the interval [0,30] seconds. * AnnouncementRequests typically happen when a Local Master Browser starts * up with an empty list of servers for the domain, and wants to fill it * quickly. The 30 second range for responses prevents the Master Browser * from becoming overloaded and losing replies, as well as preventing the * network from being flooded with responses. * * 4.4 Browser Servers * * The following sections describe the roles of the various types of * browser servers. * * 4.4.1 Potential Browser Server * * A Potential Browser server is a browser server that is capable of being * a Backup Browser server or Master Browser server, but is not currently * fulfilling either of those roles. * * A Potential Browser MUST set type SV_TYPE_POTENTIAL_BROWSER (see section * 6.4.1) in its HostAnnouncement until it is ready to shut down. In its * last HostAnnouncement frame before it shuts down, it SHOULD specify a * type of 0. * * A Potential Browser server MUST receive and process BecomeBackup frames * (see section 6.9) and become a backup browser upon their receipt. * * A Potential Browser MUST participate in browser elections (see section * 6.8). * * 4.4.2 Backup Browser * * Backup Browser servers are a subset of the Potential Browsers that have * been chosen by the Master Browser on their subnet to be the Backup * Browsers for the subnet. * * A Backup Browser MUST set type SV_TYPE_BACKUP_BROWSER (see section * 6.4.1) in its HostAnnouncement until it is ready to shut down. In its * last HostAnnouncement frame before it shuts down, it SHOULD specify a * type of 0. * * A Backup Browser MUST listen for a LocalMasterAnnouncement frame (see * section 6.10) from the Local Master Browser, and use it to set the name * of the Master Browser it queries for the server and domain lists. * * A Backup Browsers MUST periodically make a NetServerEnum2 request of * the Master Browser on its subnet for its domain to get a list of servers * in that domain, as well as a list of domains. The period is a * configuration option balancing currency of the information with network * traffic costs - a typical value is 15 minutes. * * A Backup Browser SHOULD force an election by sending a RequestElection * frame (see section 6.7) if it does not get a response to its periodic * NetServeEnum2 request to the Master Browser. * * A Backup Browser MUST receive and process NetServerEnum2 requests from * browser clients, for its own domain and others. If the request is for a * list of servers in its domain, or for a list of domains, it can answer * from its internal lists. If the request is for a list of servers in a * domain different than the one it serves, it sends a NetServerEnum2 * request to the Domain Master Browser for that domain (which it can in * find in its list of domains and their Domain Master Browsers). * * A Backup Browser MUST participate in browser elections (see section * 6.8). * * 4.4.3 Master Browser * * Master Browsers are responsible for: * . indicating it is a Master Browser * . receiving server announcements and building a list of such servers * and keeping it reasonably up-to-date. * . returning lists of Backup Browsers to browser clients. * . ensuring an appropriate number of Backup Browsers are available. * . announcing their existence to other Master Browsers on their subnet, * to the Domain Master Browser for their domain, and to all browsers in * their domain on their subnet * . forwarding requests for lists of servers on other domains to the * Master Browser for that domain * . keeping a list of domains in its subnet * . synchronizing with the Domain Master Browser (if any) for its domain * . participating in browser elections * . ensuring that there is only one Master Browser on its subnet * * A Master Browser MUST set type SV_TYPE_MASTER_BROWSER (see section * 6.4.1) in its HostAnnouncement until it is ready to shut down. In its * last HostAnnouncement frame before it shuts down, it SHOULD specify a * type of 0. * * A Master Browser MUST receive and process HostAnnouncement frames from * servers, adding the server name and other information to its servers * list; it must mark them as "local" entries. Periodically, it MUST check * all local server entries to see if a server's HostAnnouncement has timed * out (no HostAnnouncement received for three times the periodicity the * server gave in the last received HostAnnouncement) and remove timed-out * servers from its list. * * A Master Browser MUST receive and process DomainAnnouncement frames (see * section 6.12) and maintain the domain names and their associated (Local) * Master Browsers in its internal domain list until they time out; it must * mark these as "local" entries. Periodically, it MUST check all local * domain entries to see if a server's DomainAnnouncement has timed out (no * DomainAnnouncement received for three times the periodicity the server * gave in the last received DomainAnnouncement) and remove timed-out * servers from its list. * * A Master Browser MUST receive and process GetBackupListRequest frames * from clients, returning GetBackupListResponse frames containing a list * of the Backup Servers for its domain. * * A Master Browser MUST eventually send BecomeBackup frames (see section * 6.9) to one or more Potential Browser servers to increase the number of * Backup Browsers if there are not enough Backup Browsers to handle the * anticipated query load. Note: possible good times for checking for * sufficient backup browsers are after being elected, when timing out * server HostAnnouncements, and when receiving a server's HostAnnouncement * for the first time. * * A Master Browser MUST periodically announce itself and the domain it * serves to other (Local) Master Browsers on its subnet, by sending a * DomainAnnouncement frame (see section 6.12) to its subnet. * * A Master Browser MUST send a MasterAnnouncement frame (see section 6.11) * to the Domain Master Browser after it is first elected, and periodically * thereafter. This informs the Domain Master Browser of the presence of * all the Master Browsers. * * A Master Browser MUST periodically announce itself to all browsers for * its domain on its subnet by sending a LocalMasterAnnouncement frame (see * section 6.10). * * A Master Browser MUST receive and process NetServerEnum2 requests from * browser clients, for its own domain and others. If the request is for a * list of servers in its domain, or for a list of domains, it can answer * from its internal lists. Entries in its list marked "local" MUST have * the SV_TYPE_LOCAL_LIST_ONLY bit set in the returned results; it must be * clear for all other entries. If the request is for a list of servers in * a domain different than the one it serves, it sends a NetServerEnum2 * request to the Domain Master Browser for that domain (which it can in * find in its list of domains and their Domain Master Browsers). * * Note: The list of servers that the Master Browser maintains and * returns to the Backup Browsers, is limited in size to 64K of * data. This will limit the number of systems that can be in a * browse list in a single workgroup or domain to approximately two * thousand systems. * * A Master Browser SHOULD request all servers to register with it by * sending an AnnouncementRequest frame, if, on becoming the Master Browser * by winning an election, its server list is empty. Otherwise, clients * might get an incomplete list of servers until the servers' periodic * registrations fill the server list. * * If the Master Browser on a subnet is not the Primary Domain Controller * (PDC), then it is a Local Master Browser. * * A Local Master Browser MUST periodically synchronize with the Domain * Master Browser (which is the PDC). This synchronization is performed by * making a NetServerEnum2 request to the Domain Master Browser and merging * the results with its list of servers and domains. An entry from the * Domain Master Browser should be marked "non-local", and must not * overwrite an entry with the same name marked "local". The Domain Master * Browser is located as specified in Appendix B. * * A Master Browser MUST participate in browser elections (see section * 6.8). * * A Master Browser MUST, if it receives a HostAnnouncement, * DomainAnnouncement, or LocalMasterAnnouncement frame another system that * claims to be the Master Browser for its domain, demote itself from * Master Browser and force an election. This ensures that there is only * ever one Master Browser in each workgroup or domain. * * A Master Browser SHOULD, if it loses an election, become a Backup * Browser (without being told to do so by the new Master Browser). Since * it has more up-to-date information in its lists than a Potential * Browser, it is more efficient to have it be a Backup Browser than to * promote a Potential Browser. * * 4.4.3.1 Preferred Master Browser * * A Preferred Master Browser supports exactly the same protocol elements * as a Potential Browser, except as follows. * * A Preferred Master Browser MUST always force an election when it starts * up. * * A Preferred Master Browser MUST participate in browser elections (see * section 6.8). * * A Preferred Master Browser MUST set the Preferred Master bit in the * RequestElection frame (see section 6.7) to bias the election in its * favor. * * A Preferred Master Browser SHOULD, if it loses an election, * automatically become a Backup Browser, without being told to do so by * the Master Browser. * * 4.4.4 Domain Master Browser * * Since the Domain Master Browser always runs on the PDC, it must * implement all the protocols required of a PDC in addition to the * browsing protocol, and that is way beyond the scope of this * specification. * * 5. Mailslot Protocol Specification * * The only transaction allowed to a mailslot is a mailslot write. Mailslot * writes requests are encapsulated in TRANSACT SMBs. The following table * shows the interpretation of the TRANSACT SMB parameters for a mailslot * transaction: * * Name Value Description * Command SMB_COM_TRANSACTION * Name STRING name of mail slot to write; * must start with "\\MAILSLOT\\" * SetupCount 3 Always 3 for mailslot writes * Setup[0] 1 Command code == write mailslot * Setup[1] Ignored * Setup[2] Ignored * TotalDataCount n Size of data in bytes to write to * the mailslot * Data[ n ] The data to write to the mailslot * */ /* * SMB: C transact, File = \MAILSLOT\BROWSE * SMB: SMB Status = Error Success * SMB: Error class = No Error * SMB: Error code = No Error * SMB: Header: PID = 0x0000 TID = 0x0000 MID = 0x0000 UID = 0x0000 * SMB: Tree ID (TID) = 0 (0x0) * SMB: Process ID (PID) = 0 (0x0) * SMB: User ID (UID) = 0 (0x0) * SMB: Multiplex ID (MID) = 0 (0x0) * SMB: Flags Summary = 0 (0x0) * SMB: Command = C transact * SMB: Word count = 17 * SMB: Word parameters * SMB: Total parm bytes = 0 * SMB: Total data bytes = 33 * SMB: Max parm bytes = 0 * SMB: Max data bytes = 0 * SMB: Max setup words = 0 * SMB: Transact Flags Summary = 0 (0x0) * SMB: ...............0 = Leave session intact * SMB: ..............0. = Response required * SMB: Transact timeout = 0 (0x0) * SMB: Parameter bytes = 0 (0x0) * SMB: Parameter offset = 0 (0x0) * SMB: Data bytes = 33 (0x21) * SMB: Data offset = 86 (0x56) * SMB: Setup word count = 3 * SMB: Setup words * SMB: Mailslot opcode = Write mailslot * SMB: Transaction priority = 1 * SMB: Mailslot class = Unreliable (broadcast) * SMB: Byte count = 50 * SMB: Byte parameters * SMB: Path name = \MAILSLOT\BROWSE * SMB: Transaction data * SMB: Data: Number of data bytes remaining = 33 (0x0021) * * 5. Mailslot Protocol Specification * * The only transaction allowed to a mailslot is a mailslot write. Mailslot * writes requests are encapsulated in TRANSACT SMBs. The following table * shows the interpretation of the TRANSACT SMB parameters for a mailslot * transaction: * * Name Value Description * Command SMB_COM_TRANSACTION * Name STRING name of mail slot to write; * must start with "\MAILSLOT\" * SetupCount 3 Always 3 for mailslot writes * Setup[0] 1 Command code == write mailslot * Setup[1] Ignored * Setup[2] Ignored * TotalDataCount n Size of data in bytes to write to * the mailslot * Data[ n ] The data to write to the mailslot * * Magic 0xFF 'S' 'M' 'B' * smb_com a byte, the "first" command * Error a 4-byte union, ignored in a request * smb_flg a one byte set of eight flags * smb_flg2 a two byte set of 16 flags * . twelve reserved bytes, have a role * in connectionless transports (IPX, UDP?) * smb_tid a 16-bit tree ID, a mount point sorta, * 0xFFFF is this command does not have * or require a tree context * smb_pid a 16-bit process ID * smb_uid a 16-bit user ID, specific to this "session" * and mapped to a system (bona-fide) UID * smb_mid a 16-bit multiplex ID, used to differentiate * multiple simultaneous requests from the same * process (pid) (ref RPC "xid") */ int smb_browser_load_transact_header(unsigned char *buffer, int maxcnt, int data_count, int reply, char *mailbox) { smb_msgbuf_t mb; int mailboxlen; char *fmt; int result; short class = (reply == ONE_WAY_TRANSACTION) ? 2 : 0; /* * If the mailboxlen is an even number we need to pad the * header so that the data starts on a word boundary. */ fmt = "Mb4.bw20.bwwwwb.wl2.wwwwb.wwwws"; mailboxlen = strlen(mailbox) + 1; if ((mailboxlen & 0x01) == 0) { ++mailboxlen; fmt = "Mb4.bw20.bwwwwb.wl2.wwwwb.wwwws."; } bzero(buffer, maxcnt); smb_msgbuf_init(&mb, buffer, maxcnt, 0); result = smb_msgbuf_encode(&mb, fmt, SMB_COM_TRANSACTION, /* Command */ 0x18, 0x3, 17, /* Count of parameter words */ 0, /* Total Parameter words sent */ data_count, /* Total Data bytes sent */ 2, /* Max Parameters to return */ 0, /* Max data bytes to return */ 0, /* Max setup bytes to return */ reply, /* No reply */ 0xffffffff, /* Timeout */ 0, /* Parameter bytes sent */ 0, /* Parameter offset */ data_count, /* Data bytes sent */ 69 + mailboxlen, /* Data offset */ 3, /* Setup word count */ 1, /* Setup word[0] */ 0, /* Setup word[1] */ class, /* Setup word[2] */ mailboxlen + data_count, /* Total request bytes */ mailbox); /* Mailbox address */ smb_msgbuf_term(&mb); return (result); } static int smb_browser_addr_of_subnet(struct name_entry *name, smb_hostinfo_t *hinfo, struct name_entry *result) { uint32_t ipaddr, mask, saddr; addr_entry_t *addr; if (name == NULL) return (-1); if (hinfo->hi_nic.nic_smbflags & SMB_NICF_ALIAS) return (-1); ipaddr = hinfo->hi_nic.nic_ip.a_ipv4; mask = hinfo->hi_nic.nic_mask; *result = *name; addr = &name->addr_list; do { saddr = addr->sin.sin_addr.s_addr; if ((saddr & mask) == (ipaddr & mask)) { *result = *name; result->addr_list = *addr; result->addr_list.forw = result->addr_list.back = &result->addr_list; return (0); } addr = addr->forw; } while (addr != &name->addr_list); return (-1); } static int smb_browser_bcast_addr_of_subnet(struct name_entry *name, uint32_t bcast, struct name_entry *result) { if (name != NULL && name != result) *result = *name; result->addr_list.sin.sin_family = AF_INET; result->addr_list.sinlen = sizeof (result->addr_list.sin); result->addr_list.sin.sin_addr.s_addr = bcast; result->addr_list.sin.sin_port = htons(IPPORT_NETBIOS_DGM); result->addr_list.forw = result->addr_list.back = &result->addr_list; return (0); } /* * 6.5 HostAnnouncement Browser Frame * * To advertise its presence, i.e. to publish itself as being available, a * non-browser server sends a HostAnnouncement browser frame. If the server * is a member of domain "D", this frame is sent to the NETBIOS unique name * D(1d) and mailslot "\\MAILSLOT\\BROWSE". The definition of the * HostAnnouncement frame is: * * struct { * unsigned short Opcode; * unsigned char UpdateCount; * uint32_t Periodicity; * unsigned char ServerName[]; * unsigned char VersionMajor; * unsigned char VersionMinor; * uint32_t Type; * uint32_t Signature; * unsigned char Comment[]; * } * * where: * Opcode - Identifies this structure as a browser server * announcement and is defined as HostAnnouncement with a * value of decimal 1. * * UpdateCount - must be sent as zero and ignored on receipt. * * Periodicity - The announcement frequency of the server (in * seconds). The server will be removed from the browse list * if it has not been heard from in 3X its announcement * frequency. In no case will the server be removed from the * browse list before the period 3X has elapsed. Actual * implementations may take more than 3X to actually remove * the server from the browse list. * * ServerName - Null terminated ASCII server name (up to 16 bytes * in length). * * VersionMajor - The major version number of the OS the server * is running. it will be returned by NetServerEnum2. * * VersionMinor - The minor version number of the OS the server * is running. This is entirely informational and does not * have any significance for the browsing protocol. * * Type - Specifies the type of the server. The server type bits * are specified in the NetServerEnum2 section. * * Signature - The browser protocol minor version number in the * low 8 bits, the browser protocol major version number in * the next higher 8 bits and the signature 0xaa55 in the * high 16 bits of this field. Thus, for this version of the * browser protocol (1.15) this field has the value * 0xaa55010f. This may used to isolate browser servers that * are running out of revision browser software; otherwise, * it is ignored. * * Comment - Null terminated ASCII comment for the server. * Limited to 43 bytes. * * When a non-browser server starts up, it announces itself in the manner * described once every minute. The frequency of these statements is * gradually stretched to once every 12 minutes. * * Note: older non-browser servers in a domain "D" sent HostAnnouncement * frames to the NETBIOS group name D(00). Non-Browser servers supporting * version 1.15 of the browsing protocol SHOULD NOT use this NETBIOS name, * but for backwards compatibility Master Browsers MAY receive and process * HostAnnouncement frames on this name as described above for D(1d). */ static void smb_browser_send_HostAnnouncement(smb_hostinfo_t *hinfo, uint32_t next_announcement, boolean_t remove, addr_entry_t *addr, char suffix) { smb_msgbuf_t mb; int offset, announce_len, data_length; struct name_entry dest_name; unsigned char *buffer; uint32_t type; char resource_domain[SMB_PI_MAX_DOMAIN]; if (smb_getdomainname(resource_domain, SMB_PI_MAX_DOMAIN) != 0) return; (void) smb_strupr(resource_domain); if (addr == NULL) { /* Local master Browser */ smb_init_name_struct((unsigned char *)resource_domain, suffix, 0, 0, 0, 0, 0, &dest_name); if (smb_browser_bcast_addr_of_subnet(0, hinfo->hi_nic.nic_bcast, &dest_name) < 0) return; } else { smb_init_name_struct((unsigned char *)resource_domain, suffix, 0, 0, 0, 0, 0, &dest_name); dest_name.addr_list = *addr; dest_name.addr_list.forw = dest_name.addr_list.back = &dest_name.addr_list; } /* give some extra room */ buffer = malloc(MAX_DATAGRAM_LENGTH * 2); if (buffer == NULL) { syslog(LOG_DEBUG, "smb browser: HostAnnouncement: %m"); return; } data_length = 1 + 1 + 4 + 16 + 1 + 1 + 4 + 4 + strlen(hinfo->hi_nic.nic_cmnt) + 1; offset = smb_browser_load_transact_header(buffer, MAX_DATAGRAM_LENGTH, data_length, ONE_WAY_TRANSACTION, MAILSLOT_BROWSE); if (offset < 0) { free(buffer); return; } /* * A non-browser server SHOULD send a HostAnnouncement browser frame * specifying a type of 0 just prior to shutting down, to allow it to * quickly be removed from the list of available servers. */ if (remove || (!smb_netbios_running())) type = 0; else type = hinfo->hi_type; smb_msgbuf_init(&mb, buffer + offset, MAX_DATAGRAM_LENGTH - offset, 0); announce_len = smb_msgbuf_encode(&mb, "bbl16cbblls", HOST_ANNOUNCEMENT, ++hinfo->hi_updatecnt, next_announcement * 60000, /* Periodicity in MilliSeconds */ hinfo->hi_nbname, (uint8_t)hinfo->hi_version.sv_major, (uint8_t)hinfo->hi_version.sv_minor, type, SMB_SERVER_SIGNATURE, hinfo->hi_nic.nic_cmnt); if (announce_len > 0) (void) smb_netbios_datagram_send(&hinfo->hi_netname, &dest_name, buffer, offset + announce_len); free(buffer); smb_msgbuf_term(&mb); } static void smb_browser_process_AnnouncementRequest(struct datagram *datagram, char *mailbox) { smb_hostinfo_t *hinfo; uint32_t next_announcement; uint32_t delay = random() % 29; /* in seconds */ boolean_t h_found = B_FALSE; if (strcmp(mailbox, MAILSLOT_LANMAN) != 0) { syslog(LOG_DEBUG, "smb browser: wrong mailbox (%s)", mailbox); return; } smb_netbios_sleep(delay); (void) rw_rdlock(&smb_binfo.bi_hlist_rwl); hinfo = list_head(&smb_binfo.bi_hlist); while (hinfo) { if ((hinfo->hi_nic.nic_ip.a_ipv4 & hinfo->hi_nic.nic_mask) == (datagram->src.addr_list.sin.sin_addr.s_addr & hinfo->hi_nic.nic_mask)) { h_found = B_TRUE; break; } hinfo = list_next(&smb_binfo.bi_hlist, hinfo); } if (h_found) { next_announcement = hinfo->hi_nextannouce * 60 * 1000; smb_browser_send_HostAnnouncement(hinfo, next_announcement, B_FALSE, &datagram->src.addr_list, NBT_MB); } (void) rw_unlock(&smb_binfo.bi_hlist_rwl); } void * smb_browser_dispatch(void *arg) { struct datagram *datagram = (struct datagram *)arg; smb_msgbuf_t mb; int rc; unsigned char command; unsigned char parameter_words; unsigned short total_parameter_words; unsigned short total_data_count; unsigned short max_parameters_to_return; unsigned short max_data_to_return; unsigned char max_setup_bytes_to_return; unsigned short reply; unsigned short parameter_bytes_sent; unsigned short parameter_offset; unsigned short data_bytes_sent; unsigned short data_offset; unsigned char setup_word_count; unsigned short setup_word_0; unsigned short setup_word_1; unsigned short setup_word_2; unsigned short total_request_bytes; char *mailbox; unsigned char message_type; unsigned char *data; int datalen; syslog(LOG_DEBUG, "smb browser: packet received"); smb_msgbuf_init(&mb, datagram->data, datagram->data_length, 0); rc = smb_msgbuf_decode(&mb, "Mb27.bwwwwb.w6.wwwwb.wwwws", &command, /* Command */ ¶meter_words, /* Count of parameter words */ &total_parameter_words, /* Total Parameter words sent */ &total_data_count, /* Total Data bytes sent */ &max_parameters_to_return, /* Max Parameters to return */ &max_data_to_return, /* Max data bytes to return */ &max_setup_bytes_to_return, /* Max setup bytes to return */ &reply, /* No reply */ ¶meter_bytes_sent, /* Parameter bytes sent */ ¶meter_offset, /* Parameter offset */ &data_bytes_sent, /* Data bytes sent */ &data_offset, /* Data offset */ &setup_word_count, /* Setup word count */ &setup_word_0, /* Setup word[0] */ &setup_word_1, /* Setup word[1] */ &setup_word_2, /* Setup word[2] */ &total_request_bytes, /* Total request bytes */ &mailbox); /* Mailbox address */ if (rc < 0) { syslog(LOG_ERR, "smb browser: decode error"); smb_msgbuf_term(&mb); free(datagram); return (0); } data = &datagram->data[data_offset]; datalen = datagram->data_length - data_offset; /* * The PDC location protocol, i.e. anything on the \\NET * mailslot, is handled by the smb_netlogon module. */ if (strncasecmp("\\MAILSLOT\\NET\\", mailbox, 14) == 0) { smb_netlogon_receive(datagram, mailbox, data, datalen); smb_msgbuf_term(&mb); free(datagram); return (0); } /* * If it's not a netlogon message, assume it's a browser request. * This is not the most elegant way to extract the command byte * but at least we no longer use it to get the netlogon opcode. */ message_type = datagram->data[data_offset]; switch (message_type) { case ANNOUNCEMENT_REQUEST : smb_browser_process_AnnouncementRequest(datagram, mailbox); break; default: syslog(LOG_DEBUG, "smb browser: invalid message type(%d, %x)", message_type, message_type); break; } smb_msgbuf_term(&mb); free(datagram); return (0); } /* * 11.1 Registered unique names * * (00) * This name is used by all servers and clients to receive second * class mailslot messages. A system must add this name in order to * receive mailslot messages. The only browser requests that should * appear on this name are BecomeBackup, GetBackupListResp, * MasterAnnouncement, and LocalMasterAnnouncement frames. All other * datagrams (other than the expected non-browser datagrams) may be * ignored and an error logged. * * (1d) * This name is used to identify a master browser server for domain * "DOMAIN" on a subnet. A master browser server adds this name as a * unique NETBIOS name when it becomes master browser. If the attempt * to add the name fails, the master browser server assumes that there * is another master in the domain and will fail to come up. It may * log an error if the failure occurs more than 3 times in a row (this * either indicates some form of network misconfiguration or a * software error). The only requests that should appear on this name * are GetBackupListRequest and HostAnnouncement requests. All other * datagrams on this name may be ignored (and an error logged). If * running a NETBIOS name service (NBNS, such as WINS), this name * should not be registered with the NBNS. * * (1b) * This name is used to identify the Domain Master Browser for domain * "DOMAIN" (which is also the primary domain controller). It is a * unique name added only by the primary domain controller. The * primary domain controller will respond to GetBackupListRequest on * this name just as it responds to these requests on the (1d) * name. * * 11.2 Registered group names * * (01)(02)__MSBROWSE__(02)(01) * This name is used by Master Browsers to announce themselves to the * other Master Browsers on a subnet. It is added as a group name by * all Master Browser servers. The only broadcasts that should appear * on this name is DomainAnnouncement requests. All other datagrams * can be ignored. * * (00) * This name is used by clients and servers in domain "DOMAIN" to * process server announcements. The only requests that should appear * on this name that the browser is interested in are * AnnouncementRequest and NETLOGON_QUERY (to locate the PDC) packets. * All other unidentifiable requests may be ignored (and an error * logged). * * (1E) * This name is used for announcements to browsers for domain "DOMAIN" * on a subnet. This name is registered by all the browser servers in * the domain. The only requests that should appear on this name are * RequestElection and AnnouncementRequest packets. All other * datagrams may be ignored (and an error logged). * * (1C) * This name is registered by Primary Domain Controllers. */ static void smb_browser_config(void) { smb_hostinfo_t *hinfo; struct name_entry name; struct name_entry master; struct name_entry dest; struct name_entry *entry; char resource_domain[SMB_PI_MAX_DOMAIN]; int rc; if (smb_browser_init() != 0) return; if (smb_getdomainname(resource_domain, SMB_PI_MAX_DOMAIN) != 0) return; (void) smb_strupr(resource_domain); /* domain<00> */ smb_init_name_struct((unsigned char *)resource_domain, NBT_WKSTA, 0, 0, 0, 0, 0, &name); entry = smb_name_find_name(&name); smb_name_unlock_name(entry); (void) rw_rdlock(&smb_binfo.bi_hlist_rwl); hinfo = list_head(&smb_binfo.bi_hlist); while (hinfo) { smb_init_name_struct((unsigned char *)resource_domain, NBT_WKSTA, 0, hinfo->hi_nic.nic_ip.a_ipv4, htons(IPPORT_NETBIOS_DGM), NAME_ATTR_GROUP, NAME_ATTR_LOCAL, &name); (void) smb_name_add_name(&name); hinfo = list_next(&smb_binfo.bi_hlist, hinfo); } (void) rw_unlock(&smb_binfo.bi_hlist_rwl); /* All our local master browsers */ smb_init_name_struct((unsigned char *)resource_domain, NBT_MB, 0, 0, 0, 0, 0, &dest); entry = smb_name_find_name(&dest); if (entry) { (void) rw_rdlock(&smb_binfo.bi_hlist_rwl); hinfo = list_head(&smb_binfo.bi_hlist); while (hinfo) { rc = smb_browser_addr_of_subnet(entry, hinfo, &master); if (rc == 0) { syslog(LOG_DEBUG, "smb browser: master browser found at %s", inet_ntoa(master.addr_list.sin.sin_addr)); } hinfo = list_next(&smb_binfo.bi_hlist, hinfo); } (void) rw_unlock(&smb_binfo.bi_hlist_rwl); smb_name_unlock_name(entry); } /* Domain master browser */ smb_init_name_struct((unsigned char *)resource_domain, NBT_DMB, 0, 0, 0, 0, 0, &dest); if ((entry = smb_name_find_name(&dest)) != 0) { syslog(LOG_DEBUG, "smb browser: domain master browser for %s is %s", resource_domain, inet_ntoa(entry->addr_list.sin.sin_addr)); smb_name_unlock_name(entry); } } static int smb_browser_init(void) { smb_hostinfo_t *hinfo; smb_niciter_t ni; uint32_t type; smb_version_t version; smb_config_get_version(&version); (void) rw_wrlock(&smb_binfo.bi_hlist_rwl); smb_browser_infofree(); if (smb_nic_getfirst(&ni) != SMB_NIC_SUCCESS) { (void) rw_unlock(&smb_binfo.bi_hlist_rwl); return (-1); } type = MY_SERVER_TYPE; if (smb_config_get_secmode() == SMB_SECMODE_DOMAIN) type |= SV_DOMAIN_MEMBER; do { if ((ni.ni_nic.nic_smbflags & SMB_NICF_NBEXCL) || (ni.ni_nic.nic_smbflags & SMB_NICF_ALIAS)) continue; hinfo = malloc(sizeof (smb_hostinfo_t)); if (hinfo == NULL) { smb_browser_infofree(); (void) rw_unlock(&smb_binfo.bi_hlist_rwl); return (-1); } hinfo->hi_nic = ni.ni_nic; /* One Minute announcements for first five */ hinfo->hi_nextannouce = 1; hinfo->hi_interval = 1; hinfo->hi_reps = 5; hinfo->hi_updatecnt = 0; hinfo->hi_type = type; hinfo->hi_version = version; /* This is the name used for HostAnnouncement */ (void) strlcpy(hinfo->hi_nbname, hinfo->hi_nic.nic_host, NETBIOS_NAME_SZ); (void) smb_strupr(hinfo->hi_nbname); /* 0x20: file server service */ smb_init_name_struct((unsigned char *)hinfo->hi_nbname, NBT_SERVER, 0, hinfo->hi_nic.nic_ip.a_ipv4, htons(IPPORT_NETBIOS_DGM), NAME_ATTR_UNIQUE, NAME_ATTR_LOCAL, &hinfo->hi_netname); list_insert_tail(&smb_binfo.bi_hlist, hinfo); smb_binfo.bi_hcnt++; } while (smb_nic_getnext(&ni) == SMB_NIC_SUCCESS); (void) rw_unlock(&smb_binfo.bi_hlist_rwl); return (0); } /* * smb_browser_non_master_duties * * To advertise its presence, i.e. to publish itself as being available, a * non-browser server sends a HostAnnouncement browser frame. If the server * is a member of domain "D", this frame is sent to the NETBIOS unique name * D(1d) and mailslot "\\MAILSLOT\\BROWSE". */ static void smb_browser_non_master_duties(smb_hostinfo_t *hinfo, boolean_t remove) { struct name_entry name; struct name_entry *dest; addr_entry_t addr; char resource_domain[SMB_PI_MAX_DOMAIN]; smb_browser_send_HostAnnouncement(hinfo, hinfo->hi_interval, remove, 0, NBT_MB); if (smb_getdomainname(resource_domain, SMB_PI_MAX_DOMAIN) != 0) return; (void) smb_strupr(resource_domain); smb_init_name_struct((unsigned char *)resource_domain, NBT_MB, 0, 0, 0, 0, 0, &name); if ((dest = smb_name_find_name(&name))) { addr = dest->addr_list; addr.forw = addr.back = &addr; smb_name_unlock_name(dest); smb_browser_send_HostAnnouncement(hinfo, hinfo->hi_interval, remove, &addr, NBT_MB); } else { smb_init_name_struct((unsigned char *)resource_domain, NBT_DMB, 0, 0, 0, 0, 0, &name); if ((dest = smb_name_find_name(&name))) { addr = dest->addr_list; addr.forw = addr.back = &addr; smb_name_unlock_name(dest); smb_browser_send_HostAnnouncement(hinfo, remove, hinfo->hi_interval, &addr, NBT_DMB); } } /* * One Minute announcements for first five * minutes, one minute longer each round * until 12 minutes and every 12 minutes * thereafter. */ if (--hinfo->hi_reps == 0) { if (hinfo->hi_interval < 12) hinfo->hi_interval++; hinfo->hi_reps = 1; } hinfo->hi_nextannouce = hinfo->hi_interval; } /* * SMB NetBIOS Browser Service */ /*ARGSUSED*/ void * smb_browser_service(void *arg) { smb_hostinfo_t *hinfo; smb_browser_infoinit(); smb_browser_config(); smb_netbios_event(NETBIOS_EVENT_BROWSER_START); restart: do { (void) rw_rdlock(&smb_binfo.bi_hlist_rwl); hinfo = list_head(&smb_binfo.bi_hlist); while (hinfo) { if (--hinfo->hi_nextannouce > 0 || hinfo->hi_nic.nic_bcast == 0) { hinfo = list_next(&smb_binfo.bi_hlist, hinfo); continue; } smb_browser_non_master_duties(hinfo, B_FALSE); /* Check to see whether reconfig is needed */ (void) mutex_lock(&smb_binfo.bi_mtx); if (smb_binfo.bi_changed) { smb_binfo.bi_changed = B_FALSE; (void) mutex_unlock(&smb_binfo.bi_mtx); (void) rw_unlock(&smb_binfo.bi_hlist_rwl); smb_browser_config(); goto restart; } (void) mutex_unlock(&smb_binfo.bi_mtx); hinfo = list_next(&smb_binfo.bi_hlist, hinfo); } (void) rw_unlock(&smb_binfo.bi_hlist_rwl); smb_netbios_sleep(SECSPERMIN); /* 1 minute */ } while (smb_netbios_running()); smb_browser_infoterm(); smb_netbios_event(NETBIOS_EVENT_BROWSER_STOP); return (0); } /* * smb_browser_netlogon * * Sends SAMLOGON/NETLOGON request for all host/ips, except * aliases, to find a domain controller. * * The dc argument will be set if a DC is found. */ boolean_t smb_browser_netlogon(char *domain, char *dc, uint32_t dc_len) { smb_hostinfo_t *hinfo; boolean_t found = B_FALSE; timestruc_t to; int err; (void) rw_rdlock(&smb_binfo.bi_hlist_rwl); hinfo = list_head(&smb_binfo.bi_hlist); while (hinfo) { if ((hinfo->hi_nic.nic_smbflags & SMB_NICF_ALIAS) == 0) smb_netlogon_request(&hinfo->hi_netname, domain); hinfo = list_next(&smb_binfo.bi_hlist, hinfo); } (void) rw_unlock(&smb_binfo.bi_hlist_rwl); bzero(dc, dc_len); to.tv_sec = 30; to.tv_nsec = 0; (void) mutex_lock(&ntdomain_mtx); while (ntdomain_info.n_ipaddr == 0) { err = cond_reltimedwait(&ntdomain_cv, &ntdomain_mtx, &to); if (err == ETIME) break; } if (ntdomain_info.n_ipaddr != 0) { (void) strlcpy(dc, ntdomain_info.n_name, dc_len); found = B_TRUE; } (void) mutex_unlock(&ntdomain_mtx); return (found); } /* * smb_browser_infoinit * * This function is called only once when the browser starts * to initialize the global smb_binfo structure. */ static void smb_browser_infoinit(void) { (void) mutex_lock(&ntdomain_mtx); bzero(&ntdomain_info, sizeof (ntdomain_info)); (void) mutex_unlock(&ntdomain_mtx); (void) rw_wrlock(&smb_binfo.bi_hlist_rwl); list_create(&smb_binfo.bi_hlist, sizeof (smb_hostinfo_t), offsetof(smb_hostinfo_t, hi_lnd)); smb_binfo.bi_hcnt = 0; (void) rw_unlock(&smb_binfo.bi_hlist_rwl); (void) mutex_lock(&smb_binfo.bi_mtx); smb_binfo.bi_changed = B_FALSE; (void) mutex_unlock(&smb_binfo.bi_mtx); } /* * smb_browser_infoterm * * This function is called only once when the browser stops * to destroy the smb_binfo structure. */ static void smb_browser_infoterm(void) { (void) rw_wrlock(&smb_binfo.bi_hlist_rwl); smb_browser_infofree(); list_destroy(&smb_binfo.bi_hlist); (void) rw_unlock(&smb_binfo.bi_hlist_rwl); } /* * smb_browser_infofree * * Removes all the hostinfo structures from the browser list * and frees the allocated memory */ static void smb_browser_infofree(void) { smb_hostinfo_t *hinfo; while ((hinfo = list_head(&smb_binfo.bi_hlist)) != NULL) { list_remove(&smb_binfo.bi_hlist, hinfo); free(hinfo); } smb_binfo.bi_hcnt = 0; } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _BROWSER_H_ #define _BROWSER_H_ /* * NetBIOS name types describe the functionality of the registration. * A following list of NetBIOS suffixes (16th Character of the NetBIOS * Name) is detailed in Microsoft knowledge base article Q163409. * * Name Number(h) Type Usage * -------------------------------------------------------------------------- * 00 U Workstation Service * 01 U Messenger Service * <\\--__MSBROWSE__> 01 G Master Browser * 03 U Messenger Service * 06 U RAS Server Service * 1F U NetDDE Service * 20 U File Server Service * 21 U RAS Client Service * 22 U Microsoft Exchange Interchange(MSMail * Connector) * 23 U Microsoft Exchange Store * 24 U Microsoft Exchange Directory * 30 U Modem Sharing Server Service * 31 U Modem Sharing Client Service * 43 U SMS Clients Remote Control * 44 U SMS Administrators Remote Control * Tool * 45 U SMS Clients Remote Chat * 46 U SMS Clients Remote Transfer * 4C U DEC Pathworks TCPIP service on * Windows NT * 52 U DEC Pathworks TCPIP service on * Windows NT * 87 U Microsoft Exchange Message Transfer * Agent * 6A U Microsoft Exchange IMC * BE U Network Monitor Agent * BF U Network Monitor Application * 03 U Messenger Service * 00 G Domain Name * 1B U Domain Master Browser * 1C G Domain Controllers * 1D U Master Browser * 1E G Browser Service Elections * 1C G IIS * 00 U IIS * [2B] U Lotus Notes Server Service * IRISMULTICAST [2F] G Lotus Notes * IRISNAMESERVER [33] G Lotus Notes * Forte_$ND800ZA [20] U DCA IrmaLan Gateway Server Service * * Unique (U): The name may have only one IP address assigned to it. On * a network device multiple occurrences of a single name may appear to * be registered. The suffix may be the only unique character in the name. * * Group (G): A normal group; the single name may exist with many IP * addresses. WINS responds to a name query on a group name with the * limited broadcast address (255.255.255.255). Because routers block * the transmission of these addresses, the Internet Group was designed * to service communications between subnets. * * Multihomed (M): The name is unique, but due to multiple network * interfaces on the same computer this configuration is necessary to * permit the registration. The maximum number of addresses is 25. * * Internet Group (I): This is a special configuration of the group name * used to manage Windows NT Domain names. * * Domain Name (D): New in Windows NT 4.0. */ #ifdef __cplusplus extern "C" { #endif /* * Message flags used when building the SMB transact headers. */ #define TWO_WAY_TRANSACTION 0x00 #define END_SESSION_TRANSACTION 0x01 #define ONE_WAY_TRANSACTION 0x02 /* * Browser commands associated with the BROWSE and MSBROWSE mailslots. */ #define HOST_ANNOUNCEMENT 0x01 #define ANNOUNCEMENT_REQUEST 0x02 #define REQUEST_ELECTION 0x08 #define GET_BACKUP_LIST_REQ 0x09 #define GET_BACKUP_LIST_RESP 0x0A #define BECOME_BACKUP 0x0B #define DOMAIN_ANNOUNCEMENT 0x0C #define MASTER_ANNOUNCEMENT 0x0D #define LOCAL_MASTER_ANNOUNCEMENT 0x0F /* * Opcodes associated with NETLOGON or NTLOGON mailslots (KB 109626). * LOGON_REQUEST LM1.0/2.0 LOGON Request from client * LOGON_RESPONSE LM1.0 Response to LOGON_REQUEST * LOGON_CENTRAL_QUERY LM1.0 QUERY for centralized init * LOGON_DISTRIB_QUERY LM1.0 QUERY for non-centralized init * LOGON_CENTRAL_RESPONSE LM1.0 response to LOGON_CENTRAL_QUERY * LOGON_DISTRIB_RESPONSE LM1.0 resp to LOGON_DISTRIB_QUERY * LOGON_RESPONSE2 LM2.0 Response to LOGON_REQUEST * LOGON_PRIMARY_QUERY QUERY for Primary DC * LOGON_START_PRIMARY announce startup of Primary DC * LOGON_FAIL_PRIMARY announce failed Primary DC * LOGON_UAS_CHANGE announce change to UAS or SAM * LOGON_NO_USER announce no user on machine * LOGON_PRIMARY_RESPONSE response to LOGON_PRIMARY_QUERY * LOGON_RELOGON_RESPONSE LM1.0/2.0 resp to relogon request * LOGON_WKSTINFO_RESPONSE LM1.0/2.0 resp to interrogate request * LOGON_PAUSE_RESPONSE LM2.0 resp when NETLOGON is paused * LOGON_USER_UNKNOWN LM2.0 response when user is unknown * LOGON_UPDATE_ACCOUNT LM2.1 announce account updates * LOGON_SAM_LOGON_REQUEST SAM LOGON request from client * LOGON_SAM_LOGON_RESPONSE SAM Response to SAM logon request * LOGON_SAM_PAUSE_RESPONSE SAM response when NETLOGON is paused * LOGON_SAM_USER_UNKNOWN SAM response when user is unknown * LOGON_SAM_WKSTINFO_RESPONSE SAM response to interrogate request */ #define LOGON_REQUEST 0 #define LOGON_RESPONSE 1 #define LOGON_CENTRAL_QUERY 2 #define LOGON_DISTRIB_QUERY 3 #define LOGON_CENTRAL_RESPONSE 4 #define LOGON_DISTRIB_RESPONSE 5 #define LOGON_RESPONSE2 6 #define LOGON_PRIMARY_QUERY 7 #define LOGON_START_PRIMARY 8 #define LOGON_FAIL_PRIMARY 9 #define LOGON_UAS_CHANGE 10 #define LOGON_NO_USER 11 #define LOGON_PRIMARY_RESPONSE 12 #define LOGON_RELOGON_RESPONSE 13 #define LOGON_WKSTINFO_RESPONSE 14 #define LOGON_PAUSE_RESPONSE 15 #define LOGON_USER_UNKNOWN 16 #define LOGON_UPDATE_ACCOUNT 17 #define LOGON_SAM_LOGON_REQUEST 18 #define LOGON_SAM_LOGON_RESPONSE 19 #define LOGON_SAM_PAUSE_RESPONSE 20 #define LOGON_SAM_USER_UNKNOWN 21 #define LOGON_SAM_WKSTINFO_RESPONSE 22 /* * Local protocol flags used to indicate which version of the * netlogon protocol to use when attempting to find the PDC. */ #define NETLOGON_PROTO_NETLOGON 0x01 #define NETLOGON_PROTO_SAMLOGON 0x02 typedef struct smb_ntdomain_t { char n_domain[SMB_PI_MAX_DOMAIN]; char n_name[SMB_PI_MAX_DOMAIN]; uint32_t n_ipaddr; } smb_ntdomain_t; #ifdef __cplusplus } #endif #endif /* _BROWSER_H_ */ /* * 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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * The following can be removed once head/arpa/nameser_compat.h * defines BADSIG, BADKEY and BADTIME. */ #ifndef BADSIG #define BADSIG ns_r_badsig #endif /* BADSIG */ #ifndef BADKEY #define BADKEY ns_r_badkey #endif /* BADKEY */ #ifndef BADTIME #define BADTIME ns_r_badtime #endif /* BADTIME */ /* internal use, in dyndns_add_entry */ #define DEL_NONE 2 /* Maximum retires if not authoritative */ #define MAX_AUTH_RETRIES 3 /* Number of times to retry a DNS query */ #define DYNDNS_MAX_QUERY_RETRIES 3 /* Timeout value, in seconds, for DNS query responses */ #define DYNDNS_QUERY_TIMEOUT 2 static uint16_t dns_msgid; mutex_t dns_msgid_mtx; #define DYNDNS_OP_CLEAR 1 #define DYNDNS_OP_UPDATE 2 #define DYNDNS_STATE_INIT 0 #define DYNDNS_STATE_READY 1 #define DYNDNS_STATE_PUBLISHING 2 #define DYNDNS_STATE_STOPPING 3 typedef struct dyndns_qentry { list_node_t dqe_lnd; int dqe_op; /* fully-qualified domain name is in lower case */ char dqe_fqdn[MAXHOSTNAMELEN]; } dyndns_qentry_t; typedef struct dyndns_queue { list_t ddq_list; mutex_t ddq_mtx; cond_t ddq_cv; uint32_t ddq_state; } dyndns_queue_t; static dyndns_queue_t dyndns_queue; static void dyndns_queue_request(int, const char *); static void dyndns_queue_flush(list_t *); static void dyndns_process(list_t *); static int dyndns_update_core(char *); static int dyndns_clear_rev_zone(char *); static void dyndns_msgid_init(void); static int dyndns_get_msgid(void); static void dyndns_syslog(int, int, const char *); void dyndns_start(void) { (void) mutex_lock(&dyndns_queue.ddq_mtx); if (dyndns_queue.ddq_state != DYNDNS_STATE_INIT) { (void) mutex_unlock(&dyndns_queue.ddq_mtx); return; } dyndns_msgid_init(); list_create(&dyndns_queue.ddq_list, sizeof (dyndns_qentry_t), offsetof(dyndns_qentry_t, dqe_lnd)); dyndns_queue.ddq_state = DYNDNS_STATE_READY; (void) mutex_unlock(&dyndns_queue.ddq_mtx); } void dyndns_stop(void) { (void) mutex_lock(&dyndns_queue.ddq_mtx); switch (dyndns_queue.ddq_state) { case DYNDNS_STATE_READY: case DYNDNS_STATE_PUBLISHING: dyndns_queue.ddq_state = DYNDNS_STATE_STOPPING; (void) cond_signal(&dyndns_queue.ddq_cv); break; default: break; } (void) mutex_unlock(&dyndns_queue.ddq_mtx); } /* * Clear all records in both zones. */ void dyndns_clear_zones(void) { char fqdn[MAXHOSTNAMELEN]; if (smb_getfqdomainname(fqdn, MAXHOSTNAMELEN) != 0) { syslog(LOG_ERR, "dyndns: failed to get domainname"); return; } dyndns_queue_request(DYNDNS_OP_CLEAR, fqdn); } /* * Update all records in both zones. */ void dyndns_update_zones(void) { char fqdn[MAXHOSTNAMELEN]; if (smb_getfqdomainname(fqdn, MAXHOSTNAMELEN) != 0) { syslog(LOG_ERR, "dyndns: failed to get domainname"); return; } dyndns_queue_request(DYNDNS_OP_UPDATE, fqdn); } /* * Add a request to the queue. * * To comply with RFC 4120 section 6.2.1, entry->dqe_fqdn is converted * to lower case. */ static void dyndns_queue_request(int op, const char *fqdn) { dyndns_qentry_t *entry; if (!smb_config_getbool(SMB_CI_DYNDNS_ENABLE)) return; if ((entry = malloc(sizeof (dyndns_qentry_t))) == NULL) return; bzero(entry, sizeof (dyndns_qentry_t)); entry->dqe_op = op; (void) strlcpy(entry->dqe_fqdn, fqdn, MAXNAMELEN); (void) smb_strlwr(entry->dqe_fqdn); (void) mutex_lock(&dyndns_queue.ddq_mtx); switch (dyndns_queue.ddq_state) { case DYNDNS_STATE_READY: case DYNDNS_STATE_PUBLISHING: list_insert_tail(&dyndns_queue.ddq_list, entry); (void) cond_signal(&dyndns_queue.ddq_cv); break; default: free(entry); break; } (void) mutex_unlock(&dyndns_queue.ddq_mtx); } /* * Flush all remaining items from the specified list/queue. */ static void dyndns_queue_flush(list_t *lst) { dyndns_qentry_t *entry; while ((entry = list_head(lst)) != NULL) { list_remove(lst, entry); free(entry); } } /* * Dyndns update thread. While running, the thread waits on a condition * variable until notified that an entry needs to be updated. * * If the outgoing queue is not empty, the thread wakes up every 60 seconds * to retry. */ /*ARGSUSED*/ void * dyndns_publisher(void *arg) { dyndns_qentry_t *entry; list_t publist; (void) mutex_lock(&dyndns_queue.ddq_mtx); if (dyndns_queue.ddq_state != DYNDNS_STATE_READY) { (void) mutex_unlock(&dyndns_queue.ddq_mtx); return (NULL); } dyndns_queue.ddq_state = DYNDNS_STATE_PUBLISHING; (void) mutex_unlock(&dyndns_queue.ddq_mtx); list_create(&publist, sizeof (dyndns_qentry_t), offsetof(dyndns_qentry_t, dqe_lnd)); for (;;) { (void) mutex_lock(&dyndns_queue.ddq_mtx); while (list_is_empty(&dyndns_queue.ddq_list) && (dyndns_queue.ddq_state == DYNDNS_STATE_PUBLISHING)) { (void) cond_wait(&dyndns_queue.ddq_cv, &dyndns_queue.ddq_mtx); } if (dyndns_queue.ddq_state != DYNDNS_STATE_PUBLISHING) { (void) mutex_unlock(&dyndns_queue.ddq_mtx); break; } /* * Transfer queued items to the local list so that * the mutex can be released. */ while ((entry = list_head(&dyndns_queue.ddq_list)) != NULL) { list_remove(&dyndns_queue.ddq_list, entry); list_insert_tail(&publist, entry); } (void) mutex_unlock(&dyndns_queue.ddq_mtx); dyndns_process(&publist); } (void) mutex_lock(&dyndns_queue.ddq_mtx); dyndns_queue_flush(&dyndns_queue.ddq_list); list_destroy(&dyndns_queue.ddq_list); dyndns_queue.ddq_state = DYNDNS_STATE_INIT; (void) mutex_unlock(&dyndns_queue.ddq_mtx); dyndns_queue_flush(&publist); list_destroy(&publist); return (NULL); } /* * Remove items from the queue and process them. */ static void dyndns_process(list_t *publist) { dyndns_qentry_t *entry; while ((entry = list_head(publist)) != NULL) { (void) mutex_lock(&dyndns_queue.ddq_mtx); if (dyndns_queue.ddq_state != DYNDNS_STATE_PUBLISHING) { (void) mutex_unlock(&dyndns_queue.ddq_mtx); dyndns_queue_flush(publist); return; } (void) mutex_unlock(&dyndns_queue.ddq_mtx); list_remove(publist, entry); switch (entry->dqe_op) { case DYNDNS_OP_CLEAR: (void) dyndns_clear_rev_zone(entry->dqe_fqdn); break; case DYNDNS_OP_UPDATE: (void) dyndns_update_core(entry->dqe_fqdn); break; default: break; } free(entry); } } /* * Dynamic DNS update API for kclient. * * Returns 0 upon success. Otherwise, returns -1. */ int dyndns_update(char *fqdn) { int rc; if (smb_nic_init() != SMB_NIC_SUCCESS) return (-1); dyndns_msgid_init(); (void) smb_strlwr(fqdn); rc = dyndns_update_core(fqdn); smb_nic_fini(); return (rc); } /* * Initializes the DNS message ID counter using the algorithm * that resolver library uses to initialize the ID field of any res * structure. */ static void dyndns_msgid_init(void) { struct timeval now; (void) gettimeofday(&now, NULL); (void) mutex_lock(&dns_msgid_mtx); dns_msgid = (0xffff & (now.tv_sec ^ now.tv_usec ^ getpid())); (void) mutex_unlock(&dns_msgid_mtx); } static int dyndns_get_msgid(void) { uint16_t id; (void) mutex_lock(&dns_msgid_mtx); id = ++dns_msgid; (void) mutex_unlock(&dns_msgid_mtx); return (id); } /* * Log a DNS error message */ static void dyndns_syslog(int severity, int errnum, const char *text) { struct { int errnum; char *errmsg; } errtab[] = { { FORMERR, "message format error" }, { SERVFAIL, "server internal error" }, { NXDOMAIN, "entry should exist but does not exist" }, { NOTIMP, "not supported" }, { REFUSED, "operation refused" }, { YXDOMAIN, "entry should not exist but does exist" }, { YXRRSET, "RRSet should not exist but does exist" }, { NXRRSET, "RRSet should exist but does not exist" }, { NOTAUTH, "server is not authoritative for specified zone" }, { NOTZONE, "name not within specified zone" }, { BADSIG, "bad transaction signature (TSIG)" }, { BADKEY, "bad transaction key (TKEY)" }, { BADTIME, "time not synchronized" }, }; char *errmsg = "unknown error"; int i; if (errnum == NOERROR) return; for (i = 0; i < (sizeof (errtab) / sizeof (errtab[0])); ++i) { if (errtab[i].errnum == errnum) { errmsg = errtab[i].errmsg; break; } } syslog(severity, "dyndns: %s: %s: %d", text, errmsg, errnum); } /* * display_stat * Display GSS error message from error code. This routine is used to display * the mechanism independent and mechanism specific error messages for GSS * routines. The major status error code is the mechanism independent error * code and the minor status error code is the mechanism specific error code. * Parameters: * maj: GSS major status * min: GSS minor status * Returns: * None */ static void display_stat(OM_uint32 maj, OM_uint32 min) { gss_buffer_desc msg; OM_uint32 msg_ctx = 0; OM_uint32 min2; (void) gss_display_status(&min2, maj, GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &msg); syslog(LOG_ERR, "dyndns: GSS major status error: %s", (char *)msg.value); (void) gss_release_buffer(&min2, &msg); (void) gss_display_status(&min2, min, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &msg); syslog(LOG_ERR, "dyndns: GSS minor status error: %s", (char *)msg.value); (void) gss_release_buffer(&min2, &msg); } static char * dyndns_put_nshort(char *buf, uint16_t val) { uint16_t nval; nval = htons(val); (void) memcpy(buf, &nval, sizeof (uint16_t)); buf += sizeof (uint16_t); return (buf); } static char * dyndns_get_nshort(char *buf, uint16_t *val) { uint16_t nval; (void) memcpy(&nval, buf, sizeof (uint16_t)); *val = ntohs(nval); buf += sizeof (uint16_t); return (buf); } static char * dyndns_put_nlong(char *buf, uint32_t val) { uint32_t lval; lval = htonl(val); (void) memcpy(buf, &lval, sizeof (uint32_t)); buf += sizeof (uint32_t); return (buf); } static char * dyndns_put_byte(char *buf, char val) { *buf = val; buf++; return (buf); } static char * dyndns_put_int(char *buf, int val) { (void) memcpy(buf, &val, sizeof (int)); buf += sizeof (int); return (buf); } static char * dyndns_put_v6addr(char *buf, smb_inaddr_t *val) { val->a_family = AF_INET6; (void) memcpy(buf, &val->a_ipv6, IN6ADDRSZ); buf += IN6ADDRSZ; return (buf); } /* * dyndns_stuff_str * Converts a domain string by removing periods and replacing with a byte value * of how many characters following period. A byte value is placed in front * to indicate how many characters before first period. A NULL character is * placed at the end. i.e. host.procom.com -> 4host5procom3com0 * Buffer space checking is done by caller. * Parameters: * ptr : address of pointer to buffer to store converted string * zone: domain name string * Returns: * ptr: address of pointer to next available buffer space * -1 : error * 0 : success */ static int dyndns_stuff_str(char **ptr, char *zone) { int len; char *lenPtr, *zonePtr; for (zonePtr = zone; *zonePtr; ) { lenPtr = *ptr; *ptr = *ptr + 1; len = 0; while (*zonePtr != '.' && *zonePtr != 0) { *ptr = dyndns_put_byte(*ptr, *zonePtr); zonePtr++; len++; } *lenPtr = len; if (*zonePtr == '.') zonePtr++; } *ptr = dyndns_put_byte(*ptr, 0); return (0); } /* * dyndns_build_header * Build the header for DNS query and DNS update request message. * Parameters: * ptr : address of pointer to buffer to store header * buf_len : buffer length * msg_id : message id * query_req : use REQ_QUERY for query message or REQ_UPDATE for * update message * quest_zone_cnt : number of question record for query message or * number of zone record for update message * ans_prereq_cnt : number of answer record for query message or * number of prerequisite record for update message * nameser_update_cnt: number of name server for query message or * number of update record for update message * addit_cnt : number of additional record * flags : query flags word * Returns: * ptr: address of pointer to next available buffer space * -1 : error * 0 : success */ static int dyndns_build_header(char **ptr, int buf_len, uint16_t msg_id, int query_req, uint16_t quest_zone_cnt, uint16_t ans_prereq_cnt, uint16_t nameser_update_cnt, uint16_t addit_cnt, int flags) { uint16_t opcode; if (buf_len < 12) { syslog(LOG_ERR, "dyndns header section: buffer too small"); return (-1); } *ptr = dyndns_put_nshort(*ptr, msg_id); /* mesg ID */ if (query_req == REQ_QUERY) opcode = ns_o_query; /* query msg */ else opcode = ns_o_update << 11; /* update msg */ opcode |= flags; /* mesg opcode */ *ptr = dyndns_put_nshort(*ptr, opcode); /* zone record count */ *ptr = dyndns_put_nshort(*ptr, quest_zone_cnt); /* prerequiste record count */ *ptr = dyndns_put_nshort(*ptr, ans_prereq_cnt); /* update record count */ *ptr = dyndns_put_nshort(*ptr, nameser_update_cnt); /* additional record count */ *ptr = dyndns_put_nshort(*ptr, addit_cnt); return (0); } /* * dyndns_build_quest_zone * Build the question section for query message or zone section for * update message. * Parameters: * ptr : address of pointer to buffer to store question or zone section * buf_len: buffer length * name : question or zone name * type : type of question or zone * class : class of question or zone * Returns: * ptr: address of pointer to next available buffer space * -1 : error * 0 : success */ static int dyndns_build_quest_zone(char **ptr, int buf_len, char *name, int type, int class) { char *zonePtr; if ((strlen(name) + 6) > buf_len) { syslog(LOG_ERR, "dyndns question section: buffer too small"); return (-1); } zonePtr = *ptr; (void) dyndns_stuff_str(&zonePtr, name); *ptr = zonePtr; *ptr = dyndns_put_nshort(*ptr, type); *ptr = dyndns_put_nshort(*ptr, class); return (0); } /* * dyndns_build_update * Build update section of update message for adding and removing a record. * If the ttl value is 0 then this message is for record deletion. * * Parameters: * ptr : address of pointer to buffer to store update section * buf_len : buffer length * name : resource name of this record * type : type of this record * class : class of this record * ttl : time-to-live, cached time of this entry by others and not * within DNS database, a zero value for record(s) deletion * data : data of this resource record * forw_rev: UPDATE_FORW for forward zone, UPDATE_REV for reverse zone * add_del : UPDATE_ADD for adding entry, UPDATE_DEL for removing zone * del_type: DEL_ONE for deleting one entry, DEL_ALL for deleting all * entries of the same resource name. Only valid for UPDATE_DEL. * Returns: * ptr: address of pointer to next available buffer space * -1 : error * 0 : success */ static int dyndns_build_update(char **ptr, int buf_len, char *name, int type, int class, uint32_t ttl, char *data, int forw_rev, int add_del, int del_type) { char *namePtr; int rec_len, data_len; smb_inaddr_t ipaddr; int isv4 = 1; rec_len = strlen(name) + 10; if (inet_pton(AF_INET, data, &ipaddr) == 1) isv4 = 1; else if (inet_pton(AF_INET6, data, &ipaddr) == 1) isv4 = 0; if (add_del == UPDATE_ADD) { if (forw_rev == UPDATE_FORW) data_len = isv4 ? 4 : 16; else data_len = strlen(data) + 2; } else { if (del_type == DEL_ALL) data_len = 0; else if (forw_rev == UPDATE_FORW) data_len = isv4 ? 4 : 16; else data_len = strlen(data) + 2; } if (rec_len + data_len > buf_len) { syslog(LOG_ERR, "dyndns update section: buffer too small"); return (-1); } namePtr = *ptr; (void) dyndns_stuff_str(&namePtr, name); *ptr = namePtr; if (isv4) *ptr = dyndns_put_nshort(*ptr, type); else *ptr = dyndns_put_nshort(*ptr, ns_t_aaaa); *ptr = dyndns_put_nshort(*ptr, class); *ptr = dyndns_put_nlong(*ptr, ttl); if (add_del == UPDATE_DEL && del_type == DEL_ALL) { *ptr = dyndns_put_nshort(*ptr, 0); return (0); } if (forw_rev == UPDATE_FORW) { if (isv4) { *ptr = dyndns_put_nshort(*ptr, 4); *ptr = dyndns_put_int(*ptr, ipaddr.a_ipv4); } else { *ptr = dyndns_put_nshort(*ptr, 16); *ptr = dyndns_put_v6addr(*ptr, &ipaddr); } } else { *ptr = dyndns_put_nshort(*ptr, strlen(data)+2); namePtr = *ptr; (void) dyndns_stuff_str(&namePtr, data); /* hostname */ *ptr = namePtr; } return (0); } /* * dyndns_build_tkey * Build TKEY section to establish security context for secure dynamic DNS * update. DNS header and question sections need to be build before this * section. The TKEY data are the tokens generated during security context * establishment and the TKEY message is used to transmit those tokens, one * at a time, to the DNS server. * Parameters: * ptr : address of pointer to buffer to store TKEY * buf_len : buffer length * name : key name, must be unique and same as for TSIG record * key_expire: expiration time of this key in second * data : TKEY data * data_size : data size * Returns: * ptr: address of the pointer to the next available buffer space * -1 : error * 0 : success */ static int dyndns_build_tkey(char **ptr, int buf_len, char *name, int key_expire, char *data, int data_size) { char *namePtr; struct timeval tp; if (strlen(name)+2 + 45 + data_size > buf_len) { syslog(LOG_ERR, "dyndns TKEY: buffer too small"); return (-1); } namePtr = *ptr; (void) dyndns_stuff_str(&namePtr, name); /* unique global name */ *ptr = namePtr; *ptr = dyndns_put_nshort(*ptr, ns_t_tkey); *ptr = dyndns_put_nshort(*ptr, ns_c_any); *ptr = dyndns_put_nlong(*ptr, 0); /* 19 + 14 + data_size + 2 */ *ptr = dyndns_put_nshort(*ptr, 35 + data_size); namePtr = *ptr; (void) dyndns_stuff_str(&namePtr, "gss.microsoft.com"); *ptr = namePtr; (void) gettimeofday(&tp, 0); *ptr = dyndns_put_nlong(*ptr, tp.tv_sec); /* inception */ /* expiration, 86400 */ *ptr = dyndns_put_nlong(*ptr, tp.tv_sec + key_expire); *ptr = dyndns_put_nshort(*ptr, MODE_GSS_API); /* mode: gss-api */ *ptr = dyndns_put_nshort(*ptr, 0); /* error */ *ptr = dyndns_put_nshort(*ptr, data_size); /* key size */ (void) memcpy(*ptr, data, data_size); /* key data */ *ptr += data_size; *ptr = dyndns_put_nshort(*ptr, 0); /* other */ return (0); } /* * dyndns_build_tsig * Build TSIG section for secure dynamic DNS update. This routine will be * called twice. First called with TSIG_UNSIGNED, and second with TSIG_SIGNED. * The TSIG data is NULL and ignored for TSIG_UNSIGNED and is the update request * message encrypted for TSIG_SIGNED. The message id must be the same id as the * one in the update request before it is encrypted. * Parameters: * ptr : address of pointer to buffer to store TSIG * buf_len : buffer length * msg_id : message id * name : key name, must be the same as in TKEY record * fudge_time : amount of error time allow in seconds * data : TSIG data if TSIG_SIGNED, otherwise NULL * data_size : size of data, otherwise 0 if data is NULL * data_signed: TSIG_SIGNED to indicate data is signed and encrypted, * otherwise TSIG_UNSIGNED * Returns: * ptr: address of pointer to next available buffer space * -1 : error * 0 : success */ static int dyndns_build_tsig(char **ptr, int buf_len, int msg_id, char *name, int fudge_time, char *data, int data_size, int data_signed) { char *namePtr; struct timeval tp; int signtime, fudge, rec_len; if (data_signed == TSIG_UNSIGNED) rec_len = strlen(name)+2 + 37; else rec_len = strlen(name)+2 + 45 + data_size; if (rec_len > buf_len) { syslog(LOG_ERR, "dyndns TSIG: buffer too small"); return (-1); } namePtr = *ptr; (void) dyndns_stuff_str(&namePtr, name); /* unique global name */ *ptr = namePtr; if (data_signed == TSIG_SIGNED) *ptr = dyndns_put_nshort(*ptr, ns_t_tsig); *ptr = dyndns_put_nshort(*ptr, ns_c_any); *ptr = dyndns_put_nlong(*ptr, 0); if (data_signed == TSIG_SIGNED) { /* 19 + 10 + data_size + 6 */ *ptr = dyndns_put_nshort(*ptr, 35 + data_size); } namePtr = *ptr; (void) dyndns_stuff_str(&namePtr, "gss.microsoft.com"); *ptr = namePtr; (void) gettimeofday(&tp, 0); signtime = tp.tv_sec >> 16; *ptr = dyndns_put_nlong(*ptr, signtime); /* sign time */ fudge = tp.tv_sec << 16; fudge |= fudge_time; *ptr = dyndns_put_nlong(*ptr, fudge); /* fudge time */ if (data_signed == TSIG_SIGNED) { /* signed data size */ *ptr = dyndns_put_nshort(*ptr, data_size); (void) memcpy(*ptr, data, data_size); /* signed data */ *ptr += data_size; *ptr = dyndns_put_nshort(*ptr, msg_id); /* original id */ } *ptr = dyndns_put_nshort(*ptr, 0); /* error */ *ptr = dyndns_put_nshort(*ptr, 0); /* other */ return (0); } /* * dyndns_open_init_socket * This routine creates a SOCK_STREAM or SOCK_DGRAM socket and initializes it * by doing bind() and setting linger option to off. * * Parameters: * sock_type: SOCK_STREAM for TCP or SOCK_DGRAM for UDP * dest_addr: destination address in network byte order * port : destination port number * Returns: * descriptor: descriptor referencing the created socket * -1 : error */ static int dyndns_open_init_socket(int sock_type, smb_inaddr_t *dest_addr, int port) { int s; struct sockaddr_in my_addr; struct sockaddr_in6 my6_addr; struct sockaddr_in serv_addr; struct sockaddr_in6 serv6_addr; int family; family = dest_addr->a_family; if ((s = socket(family, sock_type, 0)) == -1) { syslog(LOG_ERR, "dyndns: socket error\n"); return (-1); } if (family == AF_INET) { bzero(&my_addr, sizeof (my_addr)); my_addr.sin_family = family; my_addr.sin_port = htons(0); my_addr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(s, (struct sockaddr *)&my_addr, sizeof (my_addr)) < 0) { syslog(LOG_ERR, "dyndns: client bind err\n"); (void) close(s); return (-1); } serv_addr.sin_family = family; serv_addr.sin_port = htons(port); serv_addr.sin_addr.s_addr = dest_addr->a_ipv4; if (connect(s, (struct sockaddr *)&serv_addr, sizeof (struct sockaddr_in)) < 0) { syslog(LOG_ERR, "dyndns: client connect (%s)\n", strerror(errno)); (void) close(s); return (-1); } } else { bzero(&my6_addr, sizeof (my6_addr)); my6_addr.sin6_family = family; my6_addr.sin6_port = htons(0); bzero(&my6_addr.sin6_addr.s6_addr, IN6ADDRSZ); if (bind(s, (struct sockaddr *)&my6_addr, sizeof (my6_addr)) < 0) { syslog(LOG_ERR, "dyndns: client bind err\n"); (void) close(s); return (-1); } serv6_addr.sin6_family = family; serv6_addr.sin6_port = htons(port); bcopy(&serv6_addr.sin6_addr.s6_addr, &dest_addr->a_ipv6, IN6ADDRSZ); if (connect(s, (struct sockaddr *)&serv6_addr, sizeof (struct sockaddr_in6)) < 0) { syslog(LOG_ERR, "dyndns: client connect err (%s)\n", strerror(errno)); (void) close(s); return (-1); } } return (s); } /* * dyndns_build_tkey_msg * This routine is used to build the TKEY message to transmit GSS tokens * during GSS security context establishment for secure DNS update. The * TKEY message format uses the DNS query message format. The TKEY section * is the answer section of the query message format. * Microsoft uses a value of 86400 seconds (24 hours) for key expiration time. * Parameters: * buf : buffer to build and store TKEY message * key_name: a unique key name, this same key name must be also be used in * the TSIG message * out_tok : TKEY message data (GSS tokens) * Returns: * id : message id of this TKEY message * message size: the size of the TKEY message * -1 : error */ static int dyndns_build_tkey_msg(char *buf, char *key_name, uint16_t *id, gss_buffer_desc *out_tok) { int queryReq, zoneCount, preqCount, updateCount, additionalCount; int zoneType, zoneClass; char *bufptr; queryReq = REQ_QUERY; /* query section of query request */ zoneCount = 1; /* answer section of query request */ preqCount = 1; updateCount = 0; additionalCount = 0; (void) memset(buf, 0, MAX_TCP_SIZE); bufptr = buf; *id = dyndns_get_msgid(); /* add TCP length info that follows this field */ bufptr = dyndns_put_nshort(bufptr, 26 + (strlen(key_name)+2)*2 + 35 + out_tok->length); if (dyndns_build_header(&bufptr, BUFLEN_TCP(bufptr, buf), *id, queryReq, zoneCount, preqCount, updateCount, additionalCount, 0) == -1) { return (-1); } zoneType = ns_t_tkey; zoneClass = ns_c_in; if (dyndns_build_quest_zone(&bufptr, BUFLEN_TCP(bufptr, buf), key_name, zoneType, zoneClass) == -1) { return (-1); } if (dyndns_build_tkey(&bufptr, BUFLEN_TCP(bufptr, buf), key_name, 86400, out_tok->value, out_tok->length) == -1) { return (-1); } return (bufptr - buf); } /* * dyndns_establish_sec_ctx * This routine is used to establish a security context with the DNS server * by building TKEY messages and sending them to the DNS server. TKEY messages * are also received from the DNS server for processing. The security context * establishment is done with the GSS client on the system producing a token * and sending the token within the TKEY message to the GSS server on the DNS * server. The GSS server then processes the token and then send a TKEY reply * message with a new token to be processed by the GSS client. The GSS client * processes the new token and then generates a new token to be sent to the * GSS server. This cycle is continued until the security establishment is * done. TCP is used to send and receive TKEY messages. * Parameters: * cred_handle : handle to credential * s : socket descriptor to DNS server * key_name : TKEY key name * dns_hostname: fully qualified DNS hostname which will be used for * constructing the DNS service principal name. * oid : contains Kerberos 5 object identifier * Returns: * gss_context : handle to security context */ static int dyndns_establish_sec_ctx(gss_ctx_id_t *gss_context, gss_cred_id_t cred_handle, int s, char *key_name, char *dns_hostname, gss_OID oid) { uint16_t id, rid, rsz; char buf[MAX_TCP_SIZE], buf2[MAX_TCP_SIZE]; int ret; char *service_name, *tmpptr; int service_sz; OM_uint32 min, maj, time_rec; gss_buffer_desc service_buf, in_tok, out_tok; gss_name_t target_name; gss_buffer_desc *inputptr; int gss_flags; OM_uint32 ret_flags; int buf_sz; service_sz = strlen(dns_hostname) + 5; service_name = (char *)malloc(sizeof (char) * service_sz); if (service_name == NULL) return (-1); (void) snprintf(service_name, service_sz, "DNS@%s", dns_hostname); service_buf.value = service_name; service_buf.length = strlen(service_name)+1; if ((maj = gss_import_name(&min, &service_buf, GSS_C_NT_HOSTBASED_SERVICE, &target_name)) != GSS_S_COMPLETE) { display_stat(maj, min); (void) free(service_name); return (-1); } (void) free(service_name); inputptr = GSS_C_NO_BUFFER; *gss_context = GSS_C_NO_CONTEXT; gss_flags = GSS_C_MUTUAL_FLAG | GSS_C_DELEG_FLAG | GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG; do { maj = gss_init_sec_context(&min, cred_handle, gss_context, target_name, oid, gss_flags, 0, NULL, inputptr, NULL, &out_tok, &ret_flags, &time_rec); if (maj != GSS_S_COMPLETE && maj != GSS_S_CONTINUE_NEEDED) { assert(gss_context); if (*gss_context != GSS_C_NO_CONTEXT) (void) gss_delete_sec_context(&min, gss_context, NULL); display_stat(maj, min); (void) gss_release_name(&min, &target_name); return (-1); } if ((maj == GSS_S_COMPLETE) && !(ret_flags & GSS_C_REPLAY_FLAG)) { syslog(LOG_ERR, "dyndns: No GSS_C_REPLAY_FLAG"); if (out_tok.length > 0) (void) gss_release_buffer(&min, &out_tok); (void) gss_release_name(&min, &target_name); return (-1); } if ((maj == GSS_S_COMPLETE) && !(ret_flags & GSS_C_MUTUAL_FLAG)) { syslog(LOG_ERR, "dyndns: No GSS_C_MUTUAL_FLAG"); if (out_tok.length > 0) (void) gss_release_buffer(&min, &out_tok); (void) gss_release_name(&min, &target_name); return (-1); } if (out_tok.length > 0) { if ((buf_sz = dyndns_build_tkey_msg(buf, key_name, &id, &out_tok)) <= 0) { (void) gss_release_buffer(&min, &out_tok); (void) gss_release_name(&min, &target_name); return (-1); } (void) gss_release_buffer(&min, &out_tok); if (send(s, buf, buf_sz, 0) == -1) { syslog(LOG_ERR, "dyndns: TKEY send error"); (void) gss_release_name(&min, &target_name); return (-1); } bzero(buf2, MAX_TCP_SIZE); if (recv(s, buf2, MAX_TCP_SIZE, 0) == -1) { syslog(LOG_ERR, "dyndns: TKEY recv error"); (void) gss_release_name(&min, &target_name); return (-1); } ret = buf2[5] & 0xf; /* error field in TCP */ if (ret != NOERROR) { dyndns_syslog(LOG_ERR, ret, "TKEY reply"); (void) gss_release_name(&min, &target_name); return (-1); } tmpptr = &buf2[2]; (void) dyndns_get_nshort(tmpptr, &rid); if (id != rid) { (void) gss_release_name(&min, &target_name); return (-1); } tmpptr = &buf2[59+(strlen(key_name)+2)*2]; (void) dyndns_get_nshort(tmpptr, &rsz); in_tok.length = rsz; /* bsd38 -> 2*7=14 */ in_tok.value = &buf2[61+(strlen(key_name)+2)*2]; inputptr = &in_tok; } } while (maj != GSS_S_COMPLETE); (void) gss_release_name(&min, &target_name); return (0); } /* * dyndns_get_sec_context * Get security context for secure dynamic DNS update. This routine opens * a TCP socket to the DNS server and establishes a security context with * the DNS server using host principal to perform secure dynamic DNS update. * Parameters: * hostname: fully qualified hostname * dns_ip : ip address of hostname in network byte order * Returns: * gss_handle: gss credential handle * gss_context: gss security context * -1: error * 0: success */ static gss_ctx_id_t dyndns_get_sec_context(const char *hostname, smb_inaddr_t *dns_ip) { int s; gss_cred_id_t cred_handle; gss_ctx_id_t gss_context; gss_OID oid; char *key_name, dns_hostname[MAXHOSTNAMELEN]; cred_handle = GSS_C_NO_CREDENTIAL; oid = GSS_C_NO_OID; key_name = (char *)hostname; if (smb_getnameinfo(dns_ip, dns_hostname, sizeof (dns_hostname), 0)) { return (NULL); } if ((s = dyndns_open_init_socket(SOCK_STREAM, dns_ip, 53)) < 0) { return (NULL); } if (dyndns_establish_sec_ctx(&gss_context, cred_handle, s, key_name, dns_hostname, oid)) gss_context = NULL; (void) close(s); return (gss_context); } /* * dyndns_build_add_remove_msg * This routine builds the update request message for adding and removing DNS * entries which is used for non-secure and secure DNS update. * This routine builds an UDP message. * Parameters: * buf : buffer to build message * update_zone: the type of zone to update, use UPDATE_FORW for forward * lookup zone, use UPDATE_REV for reverse lookup zone * hostname : fully qualified hostname to update DNS with * ip_addr : IP address of hostname * life_time : cached time of this entry by others and not within DNS * database * update_type: UPDATE_ADD to add entry, UPDATE_DEL to remove entry * del_type : DEL_ONE for deleting one entry, DEL_ALL for deleting all * entries of the same resource name. Only valid for UPDATE_DEL. * addit_cnt : Indicate how many record is in the additional section of * the DNS message. A value of zero is always used with * non-secure update message. For secure update message, * the value will be one because the signed TSIG message * is added as the additional record of the DNS update message. * id : DNS message ID. If a positive value then this ID value is * used, otherwise the next incremented value is used * level : This is the domain level which we send the request to, level * zero is the default level, it can go upto 2 in reverse zone * and virtually to any level in forward zone. * Returns: * buf : buffer containing update message * id : DNS message ID * int : size of update message * -1 : error * * This function is changed to handle dynamic DNS update retires to higher * authoritative domains. */ static int dyndns_build_add_remove_msg(char *buf, int update_zone, const char *hostname, const char *ip_addr, int life_time, int update_type, int del_type, int addit_cnt, uint16_t *id, int level) { int a, b, c, d; char *bufptr; int queryReq, zoneCount, preqCount, updateCount, additionalCount; char *zone, *resource, *data, zone_buf[100], resrc_buf[100]; int zoneType, zoneClass, type, class, ttl; char *p; smb_inaddr_t tmp_addr; int i, j, k; int fourcnt; queryReq = REQ_UPDATE; zoneCount = 1; preqCount = 0; updateCount = 1; additionalCount = addit_cnt; (void) memset(buf, 0, NS_PACKETSZ); bufptr = buf; if (*id == 0) *id = dyndns_get_msgid(); if (dyndns_build_header(&bufptr, BUFLEN_UDP(bufptr, buf), *id, queryReq, zoneCount, preqCount, updateCount, additionalCount, 0) == -1) { return (-1); } zoneType = ns_t_soa; zoneClass = ns_c_in; if (update_zone == UPDATE_FORW) { p = (char *)hostname; /* Try higher domains according to the level requested */ do { /* domain */ if ((zone = (char *)strchr(p, '.')) == NULL) return (-1); zone += 1; p = zone; } while (--level >= 0); resource = (char *)hostname; data = (char *)ip_addr; } else { if (inet_pton(AF_INET, ip_addr, &tmp_addr) == 1) { (void) sscanf(ip_addr, "%d.%d.%d.%d", &a, &b, &c, &d); (void) sprintf(zone_buf, "%d.%d.%d.in-addr.arpa", c, b, a); zone = p = zone_buf; /* Try higher domains based on level requested */ while (--level >= 0) { /* domain */ if ((zone = (char *)strchr(p, '.')) == NULL) { return (-1); } zone += 1; p = zone; } (void) sprintf(resrc_buf, "%d.%d.%d.%d.in-addr.arpa", d, c, b, a); } else { /* * create reverse nibble ipv6 format */ bzero(resrc_buf, 100); i = 0; j = 0; while (ip_addr[i] != 0) i++; i--; while (i >= 0) { fourcnt = 3; while ((i >= 0) && (ip_addr[i] != ':')) { resrc_buf[j++] = ip_addr[i]; (void) strcat(&resrc_buf[j++], "."); fourcnt --; i--; } for (k = 0; k <= fourcnt; k++) { resrc_buf[j++] = '0'; (void) strcat(&resrc_buf[j++], "."); } i--; } (void) strcat(resrc_buf, "ip6.arpa"); (void) strcpy(zone_buf, &resrc_buf[32]); zone = zone_buf; } resource = resrc_buf; /* ip info */ data = (char *)hostname; } if (dyndns_build_quest_zone(&bufptr, BUFLEN_UDP(bufptr, buf), zone, zoneType, zoneClass) == -1) { return (-1); } if (update_zone == UPDATE_FORW) type = ns_t_a; else type = ns_t_ptr; if (update_type == UPDATE_ADD) { class = ns_c_in; ttl = life_time; } else { if (del_type == DEL_ONE) class = ns_c_none; /* remove one */ else class = ns_c_any; /* remove all */ ttl = 0; } if (dyndns_build_update(&bufptr, BUFLEN_UDP(bufptr, buf), resource, type, class, ttl, data, update_zone, update_type, del_type) == -1) { return (-1); } return (bufptr - buf); } /* * dyndns_build_unsigned_tsig_msg * This routine is used to build the unsigned TSIG message for signing. The * unsigned TSIG message contains the update request message with certain TSIG * fields included. An error time of 300 seconds is used for fudge time. This * is the number used by Microsoft clients. * This routine builds a UDP message. * Parameters: * buf : buffer to build message * update_zone: the type of zone to update, use UPDATE_FORW for forward * lookup zone, use UPDATE_REV for reverse lookup zone * hostname : fully qualified hostname to update DNS with * ip_addr : IP address of hostname * life_time : cached time of this entry by others and not within DNS * database * update_type: UPDATE_ADD to add entry, UPDATE_DEL to remove entry * del_type : DEL_ONE for deleting one entry, DEL_ALL for deleting all * entries of the same resource name. Only valid for UPDATE_DEL. * key_name : same key name used in TKEY message * id : DNS message ID. If a positive value then this ID value is * used, otherwise the next incremented value is used * level : This is the domain level which we send the request to, level * zero is the default level, it can go upto 2 in reverse zone * and virtually to any level in forward zone. * Returns: * buf : buffer containing update message * id : DNS message ID * int : size of update message * -1 : error */ static int dyndns_build_unsigned_tsig_msg(char *buf, int update_zone, const char *hostname, const char *ip_addr, int life_time, int update_type, int del_type, char *key_name, uint16_t *id, int level) { char *bufptr; int buf_sz; if ((buf_sz = dyndns_build_add_remove_msg(buf, update_zone, hostname, ip_addr, life_time, update_type, del_type, 0, id, level)) <= 0) { return (-1); } bufptr = buf + buf_sz; if (dyndns_build_tsig(&bufptr, BUFLEN_UDP(bufptr, buf), 0, key_name, 300, NULL, 0, TSIG_UNSIGNED) == -1) { return (-1); } return (bufptr - buf); } /* * dyndns_build_signed_tsig_msg * This routine build the signed TSIG message which contains the update * request message encrypted. An error time of 300 seconds is used for fudge * time. This is the number used by Microsoft clients. * This routine builds a UDP message. * Parameters: * buf : buffer to build message * update_zone: the type of zone to update, use UPDATE_FORW for forward * lookup zone, use UPDATE_REV for reverse lookup zone * hostname : fully qualified hostname to update DNS with * ip_addr : IP address of hostname * life_time : cached time of this entry by others and not within DNS * database * update_type: UPDATE_ADD to add entry, UPDATE_DEL to remove entry * del_type : DEL_ONE for deleting one entry, DEL_ALL for deleting all * entries of the same resource name. Only valid for UPDATE_DEL. * key_name : same key name used in TKEY message * id : DNS message ID. If a positive value then this ID value is * used, otherwise the next incremented value is used * in_mic : the update request message encrypted * level : This is the domain level which we send the request to, level * zero is the default level, it can go upto 2 in reverse zone * and virtually to any level in forward zone. * * Returns: * buf : buffer containing update message * id : DNS message ID * int : size of update message * -1 : error */ static int dyndns_build_signed_tsig_msg(char *buf, int update_zone, const char *hostname, const char *ip_addr, int life_time, int update_type, int del_type, char *key_name, uint16_t *id, gss_buffer_desc *in_mic, int level) { char *bufptr; int buf_sz; if ((buf_sz = dyndns_build_add_remove_msg(buf, update_zone, hostname, ip_addr, life_time, update_type, del_type, 1, id, level)) <= 0) { return (-1); } bufptr = buf + buf_sz; if (dyndns_build_tsig(&bufptr, BUFLEN_UDP(bufptr, buf), *id, key_name, 300, in_mic->value, in_mic->length, TSIG_SIGNED) == -1) { return (-1); } return (bufptr - buf); } /* * dyndns_udp_send_recv * This routine sends and receives UDP DNS request and reply messages. * * Pre-condition: Caller must call dyndns_open_init_socket() before calling * this function. * * Parameters: * s : socket descriptor * buf : buffer containing data to send * buf_sz : size of data to send * Returns: * -1 : error * rec_buf: reply dat * 0 : success */ static int dyndns_udp_send_recv(int s, char *buf, int buf_sz, char *rec_buf) { int i, retval, addr_len; struct timeval tv, timeout; fd_set rfds; struct sockaddr_in6 from_addr; timeout.tv_usec = 0; timeout.tv_sec = DYNDNS_QUERY_TIMEOUT; for (i = 0; i <= DYNDNS_MAX_QUERY_RETRIES; i++) { if (send(s, buf, buf_sz, 0) == -1) { syslog(LOG_ERR, "dyndns: UDP send error (%s)", strerror(errno)); return (-1); } FD_ZERO(&rfds); FD_SET(s, &rfds); tv = timeout; retval = select(s+1, &rfds, NULL, NULL, &tv); if (retval == -1) { return (-1); } else if (retval > 0) { bzero(rec_buf, NS_PACKETSZ); addr_len = sizeof (struct sockaddr_in6); if (recvfrom(s, rec_buf, NS_PACKETSZ, 0, (struct sockaddr *)&from_addr, &addr_len) == -1) { syslog(LOG_ERR, "dyndns: UDP recv error "); return (-1); } break; } } /* did not receive anything */ if (i == (DYNDNS_MAX_QUERY_RETRIES + 1)) { syslog(LOG_ERR, "dyndns: max retries for UDP recv reached"); return (-1); } return (0); } /* * dyndns_sec_add_remove_entry * Perform secure dynamic DNS update after getting security context. * This routine opens a UDP socket to the DNS sever, gets the security context, * builds the unsigned TSIG message and signed TSIG message. The signed TSIG * message containing the encrypted update request message is sent to the DNS * server. The response is received and check for error. If there is no * error then credential handle and security context are released and the local * NSS cached is purged. * Parameters: * update_zone : UPDATE_FORW for forward zone, UPDATE_REV for reverse zone * hostname : fully qualified hostname * ip_addr : ip address of hostname in string format * life_time : cached time of this entry by others and not within DNS * database * max_retries : maximum retries for sending DNS update request * recv_timeout: receive timeout * update_type : UPDATE_ADD for adding entry, UPDATE_DEL for removing entry * del_type : DEL_ONE for deleting one entry, DEL_ALL for deleting all * entries of the same resource name. Only valid for UPDATE_DEL * dns_str : DNS IP address in string format * Returns: * -1: error * 0: success * * This function is enhanced to handle the case of NOTAUTH error when DNS server * is not authoritative for specified zone. In this case we need to resend the * same request to the higher authoritative domains. * This is true for both secure and unsecure dynamic DNS updates. */ static int dyndns_sec_add_remove_entry(int update_zone, const char *hostname, const char *ip_addr, int life_time, int update_type, int del_type, char *dns_str) { int s2; uint16_t id, rid; char buf[NS_PACKETSZ], buf2[NS_PACKETSZ]; int ret; OM_uint32 min, maj; gss_buffer_desc in_mic, out_mic; gss_ctx_id_t gss_context; smb_inaddr_t dns_ip; char *key_name; int buf_sz; int level = 0; assert(dns_str); assert(*dns_str); if (inet_pton(AF_INET, dns_str, &dns_ip) == 1) dns_ip.a_family = AF_INET; else if (inet_pton(AF_INET6, dns_str, &dns_ip) == 1) dns_ip.a_family = AF_INET6; sec_retry_higher: if ((gss_context = dyndns_get_sec_context(hostname, &dns_ip)) == NULL) { return (-1); } key_name = (char *)hostname; if ((s2 = dyndns_open_init_socket(SOCK_DGRAM, &dns_ip, 53)) < 0) { if (gss_context != GSS_C_NO_CONTEXT) (void) gss_delete_sec_context(&min, &gss_context, NULL); return (-1); } id = 0; if ((buf_sz = dyndns_build_unsigned_tsig_msg(buf, update_zone, hostname, ip_addr, life_time, update_type, del_type, key_name, &id, level)) <= 0) { (void) close(s2); if (gss_context != GSS_C_NO_CONTEXT) (void) gss_delete_sec_context(&min, &gss_context, NULL); return (-1); } in_mic.length = buf_sz; in_mic.value = buf; /* sign update message */ if ((maj = gss_get_mic(&min, gss_context, 0, &in_mic, &out_mic)) != GSS_S_COMPLETE) { display_stat(maj, min); (void) close(s2); if (gss_context != GSS_C_NO_CONTEXT) (void) gss_delete_sec_context(&min, &gss_context, NULL); return (-1); } if ((buf_sz = dyndns_build_signed_tsig_msg(buf, update_zone, hostname, ip_addr, life_time, update_type, del_type, key_name, &id, &out_mic, level)) <= 0) { (void) close(s2); (void) gss_release_buffer(&min, &out_mic); if (gss_context != GSS_C_NO_CONTEXT) (void) gss_delete_sec_context(&min, &gss_context, NULL); return (-1); } (void) gss_release_buffer(&min, &out_mic); if (dyndns_udp_send_recv(s2, buf, buf_sz, buf2)) { (void) close(s2); if (gss_context != GSS_C_NO_CONTEXT) (void) gss_delete_sec_context(&min, &gss_context, NULL); return (-1); } (void) close(s2); if (gss_context != GSS_C_NO_CONTEXT) (void) gss_delete_sec_context(&min, &gss_context, NULL); ret = buf2[3] & 0xf; /* error field in UDP */ /* * If it is a NOTAUTH error we should retry with higher domains * until we get a successful reply or the maximum retries is met. */ if (ret == NOTAUTH && level++ < MAX_AUTH_RETRIES) goto sec_retry_higher; /* check here for update request is successful */ if (ret != NOERROR) { dyndns_syslog(LOG_ERR, ret, "TSIG reply"); return (-1); } (void) dyndns_get_nshort(buf2, &rid); if (id != rid) return (-1); return (0); } /* * dyndns_seach_entry * Query DNS server for entry. This routine can indicate if an entry exist * or not during forward or reverse lookup. Also can indicate if the data * of the entry matched. For example, for forward lookup, the entry is * searched using the hostname and the data is the IP address. For reverse * lookup, the entry is searched using the IP address and the data is the * hostname. * Parameters: * update_zone: UPDATE_FORW for forward zone, UPDATE_REV for reverse zone * hostname : fully qualified hostname * ip_addr : ip address of hostname in string format * update_type: UPDATE_ADD for adding entry, UPDATE_DEL for removing entry * Returns: * time_out: no use * is_match: is 1 for found matching entry, otherwise 0 * 1 : an entry exist but not necessarily match * 0 : an entry does not exist */ /*ARGSUSED*/ static int dyndns_search_entry(int update_zone, const char *hostname, const char *ip_addr, int update_type, struct timeval *time_out, int *is_match) { smb_inaddr_t ipaddr, dnsip; char dns_hostname[NI_MAXHOST]; struct addrinfo hints, *res = NULL; int salen; int family; *is_match = 0; if (inet_pton(AF_INET, ip_addr, &ipaddr) == 1) { salen = sizeof (ipaddr.a_ipv4); family = AF_INET; } else if (inet_pton(AF_INET6, ip_addr, &ipaddr) == 1) { salen = sizeof (ipaddr.a_ipv6); family = AF_INET6; } if (update_zone == UPDATE_FORW) { bzero((char *)&hints, sizeof (hints)); hints.ai_family = family; hints.ai_flags = AI_NUMERICHOST; if (getaddrinfo(hostname, NULL, &hints, &res)) { return (0); } if (res) { /* * if both ips aren't the same family skip to * the next record */ do { if ((res->ai_family == AF_INET) && (family == AF_INET)) { (void) memcpy(&dnsip, &res->ai_addr[0], salen); if (ipaddr.a_ipv4 == dnsip.a_ipv4) { *is_match = 1; break; } } else if ((res->ai_family == AF_INET6) && (family == AF_INET6)) { (void) memcpy(&dnsip, &res->ai_addr[0], salen); /* need compare macro here */ if (!memcmp(&ipaddr, &dnsip, IN6ADDRSZ)) { *is_match = 1; break; } } } while (res->ai_next); freeaddrinfo(res); return (1); } } else { if (smb_getnameinfo(&ipaddr, dns_hostname, NI_MAXHOST, 0)) return (0); if (strncasecmp(dns_hostname, hostname, strlen(hostname)) == 0) { *is_match = 1; } return (1); } /* entry does not exist */ return (0); } /* * dyndns_add_remove_entry * Perform non-secure dynamic DNS update. If it fails and host principal * keys can be found in the local keytab file, secure update will be performed. * * This routine opens a UDP socket to the DNS sever, build the update request * message, and sends the message to the DNS server. The response is received * and check for error. If there is no error then the local NSS cached is * purged. DNS may be used to check to see if an entry already exist before * adding or to see if an entry does exist before removing it. Adding * duplicate entries or removing non-existing entries does not cause any * problems. DNS is not check when doing a delete all. * Parameters: * update_zone: UPDATE_FORW for forward zone, UPDATE_REV for reverse zone * hostname : fully qualified hostname * ip_addr : ip address of hostname in string format * life_time : cached time of this entry by others and not within DNS * database * update_type: UPDATE_ADD to add entry, UPDATE_DEL to remove entry * do_check : DNS_CHECK to check first in DNS, DNS_NOCHECK for no DNS * checking before update * del_type : DEL_ONE for deleting one entry, DEL_ALL for deleting all * entries of the same resource name. Only valid for UPDATE_DEL. * dns_str : DNS IP address in string format * Returns: * -1: error * 0: success * * This function is enhanced to handle the case of NOTAUTH error when DNS server * is not authoritative for specified zone. In this case we need to resend the * same request to the higher authoritative domains. * This is true for both secure and unsecure dynamic DNS updates. */ static int dyndns_add_remove_entry(int update_zone, const char *hostname, const char *ip_addr, int life_time, int update_type, int do_check, int del_type, char *dns_str) { int s; uint16_t id, rid; char buf[NS_PACKETSZ], buf2[NS_PACKETSZ]; int ret; int is_exist, is_match; struct timeval timeout; int buf_sz; int level = 0; smb_inaddr_t dns_ip; char *fqdn; char *p; assert(dns_str); assert(*dns_str); if (do_check == DNS_CHECK && del_type != DEL_ALL) { is_exist = dyndns_search_entry(update_zone, hostname, ip_addr, update_type, &timeout, &is_match); if (update_type == UPDATE_ADD && is_exist && is_match) { return (0); } else if (update_type == UPDATE_DEL && !is_exist) { return (0); } } if (inet_pton(AF_INET, dns_str, &dns_ip) == 1) dns_ip.a_family = AF_INET; else if (inet_pton(AF_INET6, dns_str, &dns_ip) == 1) dns_ip.a_family = AF_INET6; retry_higher: if ((s = dyndns_open_init_socket(SOCK_DGRAM, &dns_ip, 53)) < 0) return (-1); id = 0; if ((buf_sz = dyndns_build_add_remove_msg(buf, update_zone, hostname, ip_addr, life_time, update_type, del_type, 0, &id, level)) <= 0) { (void) close(s); return (-1); } if (dyndns_udp_send_recv(s, buf, buf_sz, buf2)) { (void) close(s); return (-1); } (void) close(s); ret = buf2[3] & 0xf; /* error field in UDP */ /* * If it is a NOTAUTH error we should retry with higher domains * until we get a successful reply */ if (ret == NOTAUTH && level++ < MAX_AUTH_RETRIES) goto retry_higher; /* check here for update request is successful */ if (ret == NOERROR) { (void) dyndns_get_nshort(buf2, &rid); if (id != rid) return (-1); return (0); } if (ret == NOTIMP) { dyndns_syslog(LOG_NOTICE, NOTIMP, "dynamic updates"); return (-1); } else if (ret == NOTAUTH) { dyndns_syslog(LOG_NOTICE, NOTAUTH, "DNS"); return (-1); } if ((p = strchr(hostname, '.')) == NULL) return (-1); fqdn = ++p; if (smb_krb5_kt_find(SMB_KRB5_PN_ID_HOST_FQHN, fqdn, SMBNS_KRB5_KEYTAB)) { ret = dyndns_sec_add_remove_entry(update_zone, hostname, ip_addr, life_time, update_type, del_type, dns_str); } else { syslog(LOG_NOTICE, "dyndns: secure update failed: cannot find " "host principal \"%s\" in local keytab file.", hostname); } return (ret); } /* * dyndns_add_entry * Main routine to add an entry into DNS. The attempt will be made on the * the servers returned by smb_get_nameserver(). Upon a successful * attempt on any one of the server, the function will exit with 0. * Otherwise, -1 is retuned to indicate the update attempt on all the * nameservers has failed. * * Parameters: * update_zone: the type of zone to update, use UPDATE_FORW for forward * lookup zone, use UPDATE_REV for reverse lookup zone * hostname : fully qualified hostname * ip_addr : ip address of hostname in string format * life_time : cached time of this entry by others and not within DNS * database * Returns: * -1: error * 0: success */ static int dyndns_add_entry(int update_zone, const char *hostname, const char *ip_addr, int life_time) { const char *dns_str; char *which_zone; smb_inaddr_t ns_list[MAXNS]; char dns_buf[INET6_ADDRSTRLEN]; int i, cnt; int rc = 0; if (hostname == NULL || ip_addr == NULL) { return (-1); } cnt = smb_get_nameservers(&ns_list[0], MAXNS); for (i = 0; i < cnt; i++) { dns_str = smb_inet_ntop(&ns_list[i], dns_buf, SMB_IPSTRLEN(ns_list[i].a_family)); if (dns_str == NULL) continue; which_zone = (update_zone == UPDATE_FORW) ? "forward" : "reverse"; syslog(LOG_DEBUG, "dyndns %s lookup zone update %s (%s)", which_zone, hostname, ip_addr); if (dyndns_add_remove_entry(update_zone, hostname, ip_addr, life_time, UPDATE_ADD, DNS_NOCHECK, DEL_NONE, dns_buf) != -1) { rc = 1; break; } } return (rc ? 0 : -1); } /* * dyndns_remove_entry * Main routine to remove an entry or all entries of the same resource name * from DNS. The update attempt will be made on the primary DNS server. If * there is a failure then another attempt will be made on the secondary DNS * server. * Parameters: * update_zone: the type of zone to update, use UPDATE_FORW for forward * lookup zone, use UPDATE_REV for reverse lookup zone * hostname : fully qualified hostname * ip_addr : ip address of hostname in string format * del_type : DEL_ONE for deleting one entry, DEL_ALL for deleting all * entries of the same resource name. Only valid for UPDATE_DEL * Returns: * -1: error * 0: success */ static int dyndns_remove_entry(int update_zone, const char *hostname, const char *ip_addr, int del_type) { const char *dns_str; smb_inaddr_t ns_list[MAXNS]; char dns_buf[INET6_ADDRSTRLEN]; int i, cnt, scnt; if ((hostname == NULL || ip_addr == NULL)) { return (-1); } cnt = smb_get_nameservers(ns_list, MAXNS); scnt = 0; for (i = 0; i < cnt; i++) { dns_str = smb_inet_ntop(&ns_list[i], dns_buf, SMB_IPSTRLEN(ns_list[i].a_family)); if (dns_str == NULL) continue; if (update_zone == UPDATE_FORW) { if (del_type == DEL_ONE) { syslog(LOG_DEBUG, "Dynamic update " "on forward lookup " "zone for %s (%s)...\n", hostname, ip_addr); } else { syslog(LOG_DEBUG, "Removing all " "entries of %s " "in forward lookup zone...\n", hostname); } } else { if (del_type == DEL_ONE) { syslog(LOG_DEBUG, "Dynamic update " "on reverse lookup " "zone for %s (%s)...\n", hostname, ip_addr); } else { syslog(LOG_DEBUG, "Removing all " "entries of %s " "in reverse lookup zone...\n", ip_addr); } } if (dyndns_add_remove_entry(update_zone, hostname, ip_addr, 0, UPDATE_DEL, DNS_NOCHECK, del_type, dns_buf) != -1) { scnt++; break; } } if (scnt) return (0); return (-1); } /* * dyndns_update_core * Perform dynamic update on both forward and reverse lookup zone using * the specified hostname and IP addresses. Before updating DNS, existing * host entries with the same hostname in the forward lookup zone are removed * and existing pointer entries with the same IP addresses in the reverse * lookup zone are removed. After DNS update, host entries for current * hostname will show current IP addresses and pointer entries for current * IP addresses will show current hostname. * Parameters: * fqdn - fully-qualified domain name (in lower case) * * Returns: * -1: some dynamic DNS updates errors * 0: successful or DDNS disabled. */ int dyndns_update_core(char *fqdn) { int forw_update_ok, error; char my_ip[INET6_ADDRSTRLEN]; const char *my_str; smb_niciter_t ni; int rc; char fqhn[MAXHOSTNAMELEN]; if (fqdn == NULL || *fqdn == '\0') return (0); if (!smb_config_getbool(SMB_CI_DYNDNS_ENABLE)) return (0); /* * To comply with RFC 4120 section 6.2.1, the fully-qualified hostname * must be set to lower case. */ if (smb_gethostname(fqhn, MAXHOSTNAMELEN, SMB_CASE_LOWER) != 0) return (-1); (void) strlcat(fqhn, ".", MAXHOSTNAMELEN); (void) strlcat(fqhn, fqdn, MAXHOSTNAMELEN); error = 0; forw_update_ok = 0; /* * Dummy IP is okay since we are removing all using the hostname. */ if (dyndns_remove_entry(UPDATE_FORW, fqhn, "1.1.1.1", DEL_ALL) == 0) { forw_update_ok = 1; } else { error++; } if (smb_nic_getfirst(&ni) != SMB_NIC_SUCCESS) return (-1); do { if (ni.ni_nic.nic_sysflags & IFF_PRIVATE) continue; /* first try ipv4, then ipv6 */ my_str = smb_inet_ntop(&ni.ni_nic.nic_ip, my_ip, SMB_IPSTRLEN(ni.ni_nic.nic_ip.a_family)); if (my_str == NULL) { error++; continue; } if (forw_update_ok) { rc = dyndns_add_entry(UPDATE_FORW, fqhn, my_str, DDNS_TTL); if (rc == -1) error++; } rc = dyndns_remove_entry(UPDATE_REV, fqhn, my_str, DEL_ALL); if (rc == 0) { rc = dyndns_add_entry(UPDATE_REV, fqhn, my_str, DDNS_TTL); } if (rc == -1) error++; } while (smb_nic_getnext(&ni) == SMB_NIC_SUCCESS); return ((error == 0) ? 0 : -1); } /* * dyndns_clear_rev_zone * Clear the rev zone records. Must be called to clear the OLD if list * of down records prior to updating the list with new information. * * Parameters: * fqdn - fully-qualified domain name (in lower case) * Returns: * -1: some dynamic DNS updates errors * 0: successful or DDNS disabled. */ int dyndns_clear_rev_zone(char *fqdn) { int error; char my_ip[INET6_ADDRSTRLEN]; smb_niciter_t ni; int rc; char fqhn[MAXHOSTNAMELEN]; const char *my_str; if (!smb_config_getbool(SMB_CI_DYNDNS_ENABLE)) return (0); /* * To comply with RFC 4120 section 6.2.1, the fully-qualified hostname * must be set to lower case. */ if (smb_gethostname(fqhn, MAXHOSTNAMELEN, SMB_CASE_LOWER) != 0) return (-1); (void) strlcat(fqhn, ".", MAXHOSTNAMELEN); (void) strlcat(fqhn, fqdn, MAXHOSTNAMELEN); error = 0; if (smb_nic_getfirst(&ni) != SMB_NIC_SUCCESS) return (-1); do { if (ni.ni_nic.nic_sysflags & IFF_PRIVATE) continue; my_str = smb_inet_ntop(&ni.ni_nic.nic_ip, my_ip, SMB_IPSTRLEN(ni.ni_nic.nic_ip.a_family)); if (my_str == NULL) { error++; continue; } rc = dyndns_remove_entry(UPDATE_REV, fqhn, my_ip, DEL_ALL); if (rc != 0) error++; } while (smb_nic_getnext(&ni) == SMB_NIC_SUCCESS); return ((error == 0) ? 0 : -1); } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _SMBSRV_DYNDNS_H #define _SMBSRV_DYNDNS_H #include /* * Header section format: * * The header contains the following fields: * * 1 1 1 1 1 1 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * | ID | * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * |QR| Opcode |AA|TC|RD|RA| Z | RCODE | * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * | QDCOUNT | * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * | ANCOUNT | * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * | NSCOUNT | * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * | ARCOUNT | * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * * where: * * ID A 16 bit identifier assigned by the program that * generates any kind of query. This identifier is copied * the corresponding reply and can be used by the requester * to match up replies to outstanding queries. * * QR A one bit field that specifies whether this message is a * query (0), or a response (1). * * OPCODE A four bit field that specifies kind of query in this * message. This value is set by the originator of a query * and copied into the response. The values are: * * 0 a standard query (QUERY) * * 1 an inverse query (IQUERY) * * 2 a server status request (STATUS) * * 3-15 reserved for future use * * AA Authoritative Answer - this bit is valid in responses, * and specifies that the responding name server is an * authority for the domain name in question section. * * Note that the contents of the answer section may have * multiple owner names because of aliases. The AA bit * * corresponds to the name which matches the query name, or * the first owner name in the answer section. * * TC TrunCation - specifies that this message was truncated * due to length greater than that permitted on the * transmission channel. * * RD Recursion Desired - this bit may be set in a query and * is copied into the response. If RD is set, it directs * the name server to pursue the query recursively. * Recursive query support is optional. * * RA Recursion Available - this be is set or cleared in a * response, and denotes whether recursive query support is * available in the name server. * * Z Reserved for future use. Must be zero in all queries * and responses. * * RCODE Response code - this 4 bit field is set as part of * responses. The values have the following * interpretation: * * 0 No error condition * * 1 Format error - The name server was * unable to interpret the query. * * 2 Server failure - The name server was * unable to process this query due to a * problem with the name server. * * 3 Name Error - Meaningful only for * responses from an authoritative name * server, this code signifies that the * domain name referenced in the query does * not exist. * * 4 Not Implemented - The name server does * not support the requested kind of query. * * 5 Refused - The name server refuses to * perform the specified operation for * policy reasons. For example, a name * server may not wish to provide the * information to the particular requester, * or a name server may not wish to perform * a particular operation (e.g., zone * * transfer) for particular data. * * 6-15 Reserved for future use. * * QDCOUNT an unsigned 16 bit integer specifying the number of * entries in the question section. * * ANCOUNT an unsigned 16 bit integer specifying the number of * resource records in the answer section. * * NSCOUNT an unsigned 16 bit integer specifying the number of name * server resource records in the authority records * section. * * ARCOUNT an unsigned 16 bit integer specifying the number of * resource records in the additional records section. */ #include #ifdef __cplusplus extern "C" { #endif /* Other definitions: */ #define REQ_QUERY 1 /* DNS query request */ #define REQ_UPDATE 0 /* DNS update request */ #define UPDATE_FORW 1 /* Update forward lookup zone */ #define UPDATE_REV 0 /* Update reverse lookup zone */ #define UPDATE_ADD 1 /* Update add request */ #define UPDATE_DEL 0 /* Update remove request */ #define MODE_GSS_API 3 /* Key negotiation mode */ /* Max buffer size for send and receive buffer */ #define MAX_BUF_SIZE 2000 #define MAX_RETRIES 3 /* Max number of send retries if no response */ #define TSIG_SIGNED 1 /* TSIG contains signed data */ #define TSIG_UNSIGNED 0 /* TSIG does not conain signed data */ #define DNS_CHECK 1 /* Check DNS for entry */ #define DNS_NOCHECK 0 /* Don't check DNS for entry */ #define MAX_TCP_SIZE 2000 /* max tcp DNS message size */ /* Delete 1 entry */ #define DEL_ONE 1 /* Delete all entries of the same resource name */ #define DEL_ALL 0 #define DNSF_RECUR_SUPP 0x80 /* Server can do recursive queries */ #define DNSF_RECUR_QRY 0x100 /* Query is recursive */ #define BUFLEN_TCP(x, y) (MAX_TCP_SIZE-(x-y)) #define BUFLEN_UDP(x, y) (NS_PACKETSZ-(x-y)) /* * DDNS_TTL is the time to live in DNS caches. Note that this * does not affect the entry in the authoritative DNS database. */ #define DDNS_TTL 1200 #ifdef __cplusplus } #endif #endif /* _SMBSRV_DYNDNS_H */ /* * 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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2014 Nexenta Systems, Inc. All rights reserved. */ /* * Copyright 1990 by the Massachusetts Institute of Technology. * All Rights Reserved. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * * Initialize a credentials cache. */ #include #include #include #include #include #include #include #include int smb_kinit(char *domain_name, char *principal_name, char *principal_passwd) { char default_realm[MAXHOSTNAMELEN]; krb5_context ctx = NULL; krb5_ccache cc = NULL; krb5_principal me = NULL; krb5_creds my_creds; krb5_error_code code; const char *doing = NULL; smb_ads_status_t err; assert(principal_name != NULL); assert(principal_passwd != NULL); (void) memset(&my_creds, 0, sizeof (my_creds)); /* * From this point on, we can goto cleanup because the key variables * are initialized. */ code = krb5_init_context(&ctx); if (code) { err = SMB_ADS_KRB5_INIT_CTX; doing = "smbns_krb: initializing context"; goto cleanup; } /* * In case krb5.conf is not configured, set the default realm. */ (void) strlcpy(default_realm, domain_name, sizeof (default_realm)); (void) smb_strupr(default_realm); (void) krb5_set_default_realm(ctx, default_realm); code = krb5_cc_default(ctx, &cc); if (code != 0) { err = SMB_ADS_KRB5_CC_DEFAULT; doing = "smbns_krb: resolve default credentials cache"; goto cleanup; } /* Use specified name */ code = krb5_parse_name(ctx, principal_name, &me); if (code != 0) { err = SMB_ADS_KRB5_PARSE_PRINCIPAL; doing = "smbns_krb: parsing principal name"; goto cleanup; } code = krb5_get_init_creds_password(ctx, &my_creds, me, principal_passwd, NULL, 0, (krb5_deltat)0, NULL, NULL); if (code != 0) { doing = "smbns_krb: getting initial credentials"; switch (code) { case KRB5KRB_AP_ERR_BAD_INTEGRITY: err = SMB_ADS_KRB5_GET_INIT_CREDS_PW; break; case KRB5KRB_AP_ERR_SKEW: err = SMB_ADS_KRB5_GET_INIT_CREDS_SKEW; break; default: err = SMB_ADS_KRB5_GET_INIT_CREDS_OTHER; break; } goto cleanup; } code = krb5_cc_initialize(ctx, cc, me); if (code != 0) { err = SMB_ADS_KRB5_CC_INITIALIZE; doing = "smbns_krb: initializing cache"; goto cleanup; } code = krb5_cc_store_cred(ctx, cc, &my_creds); if (code != 0) { err = SMB_ADS_KRB5_CC_STORE_CRED; doing = "smbns_krb: storing credentials"; goto cleanup; } /* SUCCESS! */ err = SMB_ADS_SUCCESS; cleanup: if (code != 0) { smb_krb5_log_errmsg(ctx, doing, code); } if (my_creds.client == me) { my_creds.client = NULL; } krb5_free_cred_contents(ctx, &my_creds); if (me) krb5_free_principal(ctx, me); if (cc) (void) krb5_cc_close(ctx, cc); if (ctx) krb5_free_context(ctx); return (err); } /* * Invoke krb5_get_error_message() to generate a richer error message. */ void smb_krb5_log_errmsg(krb5_context ctx, const char *prefix, krb5_error_code code) { const char *msg; msg = krb5_get_error_message(ctx, code); syslog(LOG_ERR, "%s (%s)", prefix, msg); krb5_free_error_message(ctx, msg); } /* * smb_ccache_init * * Creates the directory where the Kerberos ccache file is located * and set KRB5CCNAME in the environment. * * Returns 0 upon succcess. Otherwise, returns * -1 if it fails to create the specified directory fails. * -2 if it fails to set the KRB5CCNAME environment variable. */ int smb_ccache_init(char *dir, char *filename) { static char buf[MAXPATHLEN]; if ((mkdir(dir, 0700) < 0) && (errno != EEXIST)) return (-1); (void) snprintf(buf, MAXPATHLEN, "KRB5CCNAME=%s/%s", dir, filename); if (putenv(buf) != 0) return (-2); return (0); } void smb_ccache_remove(char *path) { if ((remove(path) < 0) && (errno != ENOENT)) syslog(LOG_ERR, "smbns_krb: failed to remove ccache (%s)", path); } /* * 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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2014 Nexenta Systems, Inc. All rights reserved. */ #ifndef _SMBSRV_SMB_KRB_H #define _SMBSRV_SMB_KRB_H #include #ifdef __cplusplus extern "C" { #endif #define SMBNS_KRB5_KEYTAB "/etc/krb5/krb5.keytab" #define SMBNS_KRB5_KEYTAB_TMP "/etc/krb5/krb5.keytab.tmp.XXXXXX" #define SMB_PN_SPN_ATTR 0x0001 /* w/o REALM portion */ #define SMB_PN_UPN_ATTR 0x0002 /* w/ REALM */ #define SMB_PN_KEYTAB_ENTRY 0x0004 /* w/ REALM */ #define SMB_PN_SALT 0x0008 /* w/ REALM */ #define SMB_PN_SVC_HOST "host" #define SMB_PN_SVC_CIFS "cifs" #define SMB_PN_SVC_NFS "nfs" #define SMB_PN_SVC_HTTP "HTTP" #define SMB_PN_SVC_ROOT "root" /* Assign an identifier for each principal name format */ typedef enum smb_krb5_pn_id { SMB_KRB5_PN_ID_SALT, SMB_KRB5_PN_ID_HOST_FQHN, /* fully qualified name */ SMB_KRB5_PN_ID_HOST_SHORT, /* short name */ SMB_KRB5_PN_ID_CIFS_FQHN, SMB_KRB5_PN_ID_CIFS_SHORT, SMB_KRB5_PN_ID_MACHINE, /* the machine account */ SMB_KRB5_PN_ID_NFS_FQHN, SMB_KRB5_PN_ID_HTTP_FQHN, SMB_KRB5_PN_ID_ROOT_FQHN, } smb_krb5_pn_id_t; /* * A principal name can be constructed based on the following: * * p_id - identifier for a principal name. * p_svc - service with which the principal is associated. * p_flags - usage of the principal is identified - whether it can be used as a * SPN attribute, UPN attribute, or/and keytab entry, etc. */ typedef struct smb_krb5_pn { smb_krb5_pn_id_t p_id; char *p_svc; uint32_t p_flags; } smb_krb5_pn_t; /* * A set of principal names * * ps_cnt - the number of principal names in the array. * ps_set - An array of principal names terminated with a NULL pointer. */ typedef struct smb_krb5_pn_set { uint32_t s_cnt; char **s_pns; } smb_krb5_pn_set_t; int smb_kinit(char *, char *, char *); int smb_krb5_ctx_init(krb5_context *); void smb_krb5_ctx_fini(krb5_context); int smb_krb5_get_kprincs(krb5_context, char **, size_t, krb5_principal **); void smb_krb5_free_kprincs(krb5_context, krb5_principal *, size_t); int smb_krb5_setpwd(krb5_context, const char *, char *); int smb_krb5_kt_populate(krb5_context, const char *, krb5_principal *, int, char *, krb5_kvno, char *, krb5_enctype *, int); boolean_t smb_krb5_kt_find(smb_krb5_pn_id_t, const char *, char *); uint32_t smb_krb5_get_pn_set(smb_krb5_pn_set_t *, uint32_t, char *); void smb_krb5_free_pn_set(smb_krb5_pn_set_t *); void smb_krb5_log_errmsg(krb5_context, const char *, krb5_error_code); #ifdef __cplusplus } #endif #endif /* _SMBSRV_SMB_KRB_H */ /* * 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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2014 Nexenta Systems, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Kerberized services available on the system. */ static smb_krb5_pn_t smb_krb5_pn_tab[] = { /* * Service keys are salted with the SMB_KRB_PN_ID_ID_SALT prinipal * name. */ {SMB_KRB5_PN_ID_SALT, SMB_PN_SVC_HOST, SMB_PN_SALT}, /* CIFS SPNs. (HOST, CIFS, ...) */ {SMB_KRB5_PN_ID_HOST_FQHN, SMB_PN_SVC_HOST, SMB_PN_KEYTAB_ENTRY | SMB_PN_SPN_ATTR | SMB_PN_UPN_ATTR}, {SMB_KRB5_PN_ID_HOST_SHORT, SMB_PN_SVC_HOST, SMB_PN_KEYTAB_ENTRY | SMB_PN_SPN_ATTR}, {SMB_KRB5_PN_ID_CIFS_FQHN, SMB_PN_SVC_CIFS, SMB_PN_KEYTAB_ENTRY | SMB_PN_SPN_ATTR}, {SMB_KRB5_PN_ID_CIFS_SHORT, SMB_PN_SVC_CIFS, SMB_PN_KEYTAB_ENTRY | SMB_PN_SPN_ATTR}, {SMB_KRB5_PN_ID_MACHINE, NULL, SMB_PN_KEYTAB_ENTRY}, /* NFS */ {SMB_KRB5_PN_ID_NFS_FQHN, SMB_PN_SVC_NFS, SMB_PN_KEYTAB_ENTRY | SMB_PN_SPN_ATTR}, /* HTTP */ {SMB_KRB5_PN_ID_HTTP_FQHN, SMB_PN_SVC_HTTP, SMB_PN_KEYTAB_ENTRY | SMB_PN_SPN_ATTR}, /* ROOT */ {SMB_KRB5_PN_ID_ROOT_FQHN, SMB_PN_SVC_ROOT, SMB_PN_KEYTAB_ENTRY | SMB_PN_SPN_ATTR}, }; #define SMB_KRB5_SPN_TAB_SZ \ (sizeof (smb_krb5_pn_tab) / sizeof (smb_krb5_pn_tab[0])) #define SMB_KRB5_MAX_BUFLEN 128 static int smb_krb5_kt_open(krb5_context, char *, krb5_keytab *); static int smb_krb5_kt_addkey(krb5_context, krb5_keytab, const krb5_principal, krb5_enctype, krb5_kvno, const krb5_data *, const char *); static int smb_krb5_spn_count(uint32_t); static smb_krb5_pn_t *smb_krb5_lookup_pn(smb_krb5_pn_id_t); static char *smb_krb5_get_pn_by_id(smb_krb5_pn_id_t, uint32_t, const char *); static int smb_krb5_get_kprinc(krb5_context, smb_krb5_pn_id_t, uint32_t, const char *, krb5_principal *); /* * Generates a null-terminated array of principal names that * represents the list of the available Kerberized services * of the specified type (SPN attribute, UPN attribute, or * keytab entry). * * Returns the number of principal names returned via the 1st * output parameter (i.e. vals). * * Caller must invoke smb_krb5_free_spns to free the allocated * memory when finished. */ uint32_t smb_krb5_get_pn_set(smb_krb5_pn_set_t *set, uint32_t type, char *fqdn) { int cnt, i; smb_krb5_pn_t *tabent; if (!set || !fqdn) return (0); bzero(set, sizeof (smb_krb5_pn_set_t)); cnt = smb_krb5_spn_count(type); set->s_pns = (char **)calloc(cnt + 1, sizeof (char *)); if (set->s_pns == NULL) return (0); for (i = 0, set->s_cnt = 0; i < SMB_KRB5_SPN_TAB_SZ; i++) { tabent = &smb_krb5_pn_tab[i]; if (set->s_cnt == cnt) break; if ((tabent->p_flags & type) != type) continue; set->s_pns[set->s_cnt] = smb_krb5_get_pn_by_id(tabent->p_id, type, fqdn); if (set->s_pns[set->s_cnt] == NULL) { syslog(LOG_ERR, "smbns_ksetpwd: failed to obtain " "principal names: possible transient memory " "shortage"); smb_krb5_free_pn_set(set); return (0); } set->s_cnt++; } if (set->s_cnt == 0) smb_krb5_free_pn_set(set); return (set->s_cnt); } void smb_krb5_free_pn_set(smb_krb5_pn_set_t *set) { int i; if (set == NULL || set->s_pns == NULL) return; for (i = 0; i < set->s_cnt; i++) free(set->s_pns[i]); free(set->s_pns); set->s_pns = NULL; } /* * Initialize the kerberos context. * Return 0 on success. Otherwise, return -1. */ int smb_krb5_ctx_init(krb5_context *ctx) { if (krb5_init_context(ctx) != 0) return (-1); return (0); } /* * Free the kerberos context. */ void smb_krb5_ctx_fini(krb5_context ctx) { krb5_free_context(ctx); } /* * Create an array of Kerberos Princiapls given an array of principal names. * Caller must free the allocated memory using smb_krb5_free_kprincs() * upon success. * * Returns 0 on success. Otherwise, returns -1. */ int smb_krb5_get_kprincs(krb5_context ctx, char **names, size_t num, krb5_principal **krb5princs) { int i; if ((*krb5princs = calloc(num, sizeof (krb5_principal *))) == NULL) { return (-1); } for (i = 0; i < num; i++) { if (krb5_parse_name(ctx, names[i], &(*krb5princs)[i]) != 0) { smb_krb5_free_kprincs(ctx, *krb5princs, i); return (-1); } } return (0); } void smb_krb5_free_kprincs(krb5_context ctx, krb5_principal *krb5princs, size_t num) { int i; for (i = 0; i < num; i++) krb5_free_principal(ctx, krb5princs[i]); free(krb5princs); } /* * Set the workstation trust account password. * Returns 0 on success. Otherwise, returns non-zero value. */ int smb_krb5_setpwd(krb5_context ctx, const char *fqdn, char *passwd) { krb5_error_code code; krb5_ccache cc = NULL; int result_code = 0; krb5_data result_code_string, result_string; krb5_principal princ; char msg[SMB_KRB5_MAX_BUFLEN]; if (smb_krb5_get_kprinc(ctx, SMB_KRB5_PN_ID_HOST_FQHN, SMB_PN_UPN_ATTR, fqdn, &princ) != 0) return (-1); (void) memset(&result_code_string, 0, sizeof (result_code_string)); (void) memset(&result_string, 0, sizeof (result_string)); if ((code = krb5_cc_default(ctx, &cc)) != 0) { (void) snprintf(msg, sizeof (msg), "smbns_ksetpwd: failed to " "find %s", SMB_CCACHE_PATH); smb_krb5_log_errmsg(ctx, msg, code); krb5_free_principal(ctx, princ); return (-1); } code = krb5_set_password_using_ccache(ctx, cc, passwd, princ, &result_code, &result_code_string, &result_string); (void) krb5_cc_close(ctx, cc); if (code != 0) smb_krb5_log_errmsg(ctx, "smbns_ksetpwd: KPASSWD protocol " "exchange failed", code); if (result_code != 0) { syslog(LOG_ERR, "smbns_ksetpwd: KPASSWD failed: rc=%d %.*s", result_code, result_code_string.length, result_code_string.data); if (code == 0) code = EACCES; } krb5_free_principal(ctx, princ); free(result_code_string.data); free(result_string.data); return (code); } /* * Open the keytab file for writing. * The keytab should be closed by calling krb5_kt_close(). */ static int smb_krb5_kt_open(krb5_context ctx, char *fname, krb5_keytab *kt) { char *ktname; krb5_error_code code; int len; char msg[SMB_KRB5_MAX_BUFLEN]; *kt = NULL; len = snprintf(NULL, 0, "WRFILE:%s", fname) + 1; if ((ktname = malloc(len)) == NULL) { syslog(LOG_ERR, "smbns_ksetpwd: unable to open keytab %s: " "possible transient memory shortage", fname); return (-1); } (void) snprintf(ktname, len, "WRFILE:%s", fname); if ((code = krb5_kt_resolve(ctx, ktname, kt)) != 0) { (void) snprintf(msg, sizeof (msg), "smbns_ksetpwd: %s", fname); smb_krb5_log_errmsg(ctx, msg, code); free(ktname); return (-1); } free(ktname); return (0); } /* * Populate the keytab with keys of the specified key version for the * specified set of krb5 principals. All service keys will be salted by: * host/.@ */ int smb_krb5_kt_populate(krb5_context ctx, const char *fqdn, krb5_principal *princs, int count, char *fname, krb5_kvno kvno, char *passwd, krb5_enctype *enctypes, int enctype_count) { krb5_keytab kt = NULL; krb5_data salt; krb5_error_code code; krb5_principal salt_princ; int i, j; if (smb_krb5_kt_open(ctx, fname, &kt) != 0) return (-1); if (smb_krb5_get_kprinc(ctx, SMB_KRB5_PN_ID_SALT, SMB_PN_SALT, fqdn, &salt_princ) != 0) { (void) krb5_kt_close(ctx, kt); return (-1); } code = krb5_principal2salt(ctx, salt_princ, &salt); if (code != 0) { smb_krb5_log_errmsg(ctx, "smbns_ksetpwd: salt computation " "failed", code); krb5_free_principal(ctx, salt_princ); (void) krb5_kt_close(ctx, kt); return (-1); } for (j = 0; j < count; j++) { for (i = 0; i < enctype_count; i++) { if (smb_krb5_kt_addkey(ctx, kt, princs[j], enctypes[i], kvno, &salt, passwd) != 0) { krb5_free_principal(ctx, salt_princ); krb5_xfree(salt.data); (void) krb5_kt_close(ctx, kt); return (-1); } } } krb5_free_principal(ctx, salt_princ); krb5_xfree(salt.data); (void) krb5_kt_close(ctx, kt); return (0); } boolean_t smb_krb5_kt_find(smb_krb5_pn_id_t id, const char *fqdn, char *fname) { krb5_context ctx; krb5_keytab kt; krb5_keytab_entry entry; krb5_principal princ; char ktname[MAXPATHLEN]; boolean_t found = B_FALSE; if (!fqdn || !fname) return (found); if (smb_krb5_ctx_init(&ctx) != 0) return (found); if (smb_krb5_get_kprinc(ctx, id, SMB_PN_KEYTAB_ENTRY, fqdn, &princ) != 0) { smb_krb5_ctx_fini(ctx); return (found); } (void) snprintf(ktname, MAXPATHLEN, "FILE:%s", fname); if (krb5_kt_resolve(ctx, ktname, &kt) == 0) { if (krb5_kt_get_entry(ctx, kt, princ, 0, 0, &entry) == 0) { found = B_TRUE; (void) krb5_kt_free_entry(ctx, &entry); } (void) krb5_kt_close(ctx, kt); } krb5_free_principal(ctx, princ); smb_krb5_ctx_fini(ctx); return (found); } /* * Add a key of the specified encryption type for the specified principal * to the keytab file. * Returns 0 on success. Otherwise, returns -1. */ static int smb_krb5_kt_addkey(krb5_context ctx, krb5_keytab kt, const krb5_principal princ, krb5_enctype enctype, krb5_kvno kvno, const krb5_data *salt, const char *pw) { krb5_keytab_entry *entry; krb5_data password; krb5_keyblock key; krb5_error_code code; char buf[SMB_KRB5_MAX_BUFLEN], msg[SMB_KRB5_MAX_BUFLEN]; int rc = 0; if ((code = krb5_enctype_to_string(enctype, buf, sizeof (buf)))) { (void) snprintf(msg, sizeof (msg), "smbns_ksetpwd: unknown " "encryption type (%d)", enctype); smb_krb5_log_errmsg(ctx, msg, code); return (-1); } if ((entry = (krb5_keytab_entry *) malloc(sizeof (*entry))) == NULL) { syslog(LOG_ERR, "smbns_ksetpwd: possible transient " "memory shortage"); return (-1); } (void) memset((char *)entry, 0, sizeof (*entry)); password.length = strlen(pw); password.data = (char *)pw; code = krb5_c_string_to_key(ctx, enctype, &password, salt, &key); if (code != 0) { (void) snprintf(msg, sizeof (msg), "smbns_ksetpwd: failed to " "generate key (%d)", enctype); smb_krb5_log_errmsg(ctx, msg, code); free(entry); return (-1); } (void) memcpy(&entry->key, &key, sizeof (krb5_keyblock)); entry->vno = kvno; entry->principal = princ; if ((code = krb5_kt_add_entry(ctx, kt, entry)) != 0) { (void) snprintf(msg, sizeof (msg), "smbns_ksetpwd: failed to " "add key (%d)", enctype); smb_krb5_log_errmsg(ctx, msg, code); rc = -1; } free(entry); if (key.length) krb5_free_keyblock_contents(ctx, &key); return (rc); } static int smb_krb5_spn_count(uint32_t type) { int i, cnt; for (i = 0, cnt = 0; i < SMB_KRB5_SPN_TAB_SZ; i++) { if (smb_krb5_pn_tab[i].p_flags & type) cnt++; } return (cnt); } /* * Generate the Kerberos Principal given a principal name format and the * fully qualified domain name. On success, caller must free the allocated * memory by calling krb5_free_principal(). */ static int smb_krb5_get_kprinc(krb5_context ctx, smb_krb5_pn_id_t id, uint32_t type, const char *fqdn, krb5_principal *princ) { char *buf; if ((buf = smb_krb5_get_pn_by_id(id, type, fqdn)) == NULL) return (-1); if (krb5_parse_name(ctx, buf, princ) != 0) { free(buf); return (-1); } free(buf); return (0); } /* * Looks up an entry in the principal name table given the ID. */ static smb_krb5_pn_t * smb_krb5_lookup_pn(smb_krb5_pn_id_t id) { int i; smb_krb5_pn_t *tabent; for (i = 0; i < SMB_KRB5_SPN_TAB_SZ; i++) { tabent = &smb_krb5_pn_tab[i]; if (id == tabent->p_id) return (tabent); } return (NULL); } /* * Construct the principal name given an ID, the requested type, and the * fully-qualified name of the domain of which the principal is a member. */ static char * smb_krb5_get_pn_by_id(smb_krb5_pn_id_t id, uint32_t type, const char *fqdn) { char nbname[NETBIOS_NAME_SZ]; char hostname[MAXHOSTNAMELEN]; char *realm = NULL; smb_krb5_pn_t *pn; char *buf; (void) smb_getnetbiosname(nbname, NETBIOS_NAME_SZ); (void) smb_gethostname(hostname, MAXHOSTNAMELEN, SMB_CASE_LOWER); pn = smb_krb5_lookup_pn(id); /* detect inconsistent requested format and type */ if ((type & pn->p_flags) != type) return (NULL); switch (id) { case SMB_KRB5_PN_ID_SALT: (void) asprintf(&buf, "%s/%s.%s", pn->p_svc, smb_strlwr(nbname), fqdn); break; case SMB_KRB5_PN_ID_HOST_FQHN: case SMB_KRB5_PN_ID_CIFS_FQHN: case SMB_KRB5_PN_ID_NFS_FQHN: case SMB_KRB5_PN_ID_HTTP_FQHN: case SMB_KRB5_PN_ID_ROOT_FQHN: (void) asprintf(&buf, "%s/%s.%s", pn->p_svc, hostname, fqdn); break; case SMB_KRB5_PN_ID_HOST_SHORT: case SMB_KRB5_PN_ID_CIFS_SHORT: (void) asprintf(&buf, "%s/%s", pn->p_svc, nbname); break; /* * SPN for the machine account, which is simply the * (short) machine name with a dollar sign appended. */ case SMB_KRB5_PN_ID_MACHINE: (void) asprintf(&buf, "%s$", nbname); break; default: return (NULL); } /* * If the requested principal is either added to keytab / the machine * account as the UPN attribute or used for key salt generation, * the principal name must have the @ portion. */ if (type & (SMB_PN_KEYTAB_ENTRY | SMB_PN_UPN_ATTR | SMB_PN_SALT)) { if ((realm = strdup(fqdn)) == NULL) { free(buf); return (NULL); } (void) smb_strupr(realm); if (buf != NULL) { char *tmp; (void) asprintf(&tmp, "%s@%s", buf, realm); free(buf); buf = tmp; } free(realm); } return (buf); } /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Main startup code for SMB/NETBIOS and some utility routines * for the NETBIOS layer. */ #include #include #include #include #include #include #include #include #include #include #include #include #define SMB_NETBIOS_DUMP_FILE "netbios" static netbios_service_t nbtd; static void smb_netbios_shutdown(void); static void *smb_netbios_service(void *); static void smb_netbios_dump(void); /* * Start the NetBIOS services */ int smb_netbios_start(void) { pthread_t tid; pthread_attr_t attr; int rc; if (smb_netbios_cache_init() < 0) return (-1); (void) pthread_attr_init(&attr); (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); rc = pthread_create(&tid, &attr, smb_netbios_service, NULL); (void) pthread_attr_destroy(&attr); return (rc); } /* * Stop the NetBIOS services */ void smb_netbios_stop(void) { char fname[MAXPATHLEN]; smb_netbios_event(NETBIOS_EVENT_STOP); (void) snprintf(fname, MAXPATHLEN, "%s/%s", SMB_VARRUN_DIR, SMB_NETBIOS_DUMP_FILE); (void) unlink(fname); } /* * Launch the NetBIOS Name Service, Datagram and Browser services * and then sit in a loop providing a 1 second resolution timer. * The timer will: * - update the netbios stats file every 10 minutes * - clean the cache every 10 minutes */ /*ARGSUSED*/ static void * smb_netbios_service(void *arg) { static uint32_t ticks = 0; pthread_t tid; int rc; smb_netbios_event(NETBIOS_EVENT_START); rc = pthread_create(&tid, NULL, smb_netbios_name_service, NULL); if (rc != 0) { smb_netbios_shutdown(); return (NULL); } smb_netbios_wait(NETBIOS_EVENT_NS_START); if (smb_netbios_error()) { smb_netbios_shutdown(); return (NULL); } smb_netbios_name_config(); rc = pthread_create(&tid, NULL, smb_netbios_datagram_service, NULL); if (rc != 0) { smb_netbios_shutdown(); return (NULL); } smb_netbios_wait(NETBIOS_EVENT_DGM_START); if (smb_netbios_error()) { smb_netbios_shutdown(); return (NULL); } rc = pthread_create(&tid, NULL, smb_browser_service, NULL); if (rc != 0) { smb_netbios_shutdown(); return (NULL); } smb_netbios_event(NETBIOS_EVENT_TIMER_START); for (;;) { (void) sleep(1); ticks++; if (!smb_netbios_running()) break; smb_netbios_datagram_tick(); smb_netbios_name_tick(); if ((ticks % 600) == 0) { smb_netbios_event(NETBIOS_EVENT_DUMP); smb_netbios_cache_clean(); } } smb_netbios_event(NETBIOS_EVENT_TIMER_STOP); smb_netbios_shutdown(); return (NULL); } static void smb_netbios_shutdown(void) { (void) pthread_join(nbtd.nbs_browser.s_tid, 0); (void) pthread_join(nbtd.nbs_dgm.s_tid, 0); (void) pthread_join(nbtd.nbs_ns.s_tid, 0); nbtd.nbs_browser.s_tid = 0; nbtd.nbs_dgm.s_tid = 0; nbtd.nbs_ns.s_tid = 0; smb_netbios_cache_fini(); if (smb_netbios_error()) { smb_netbios_event(NETBIOS_EVENT_RESET); if (smb_netbios_start() != 0) syslog(LOG_ERR, "netbios: restart failed"); } } int smb_first_level_name_encode(struct name_entry *name, unsigned char *out, int max_out) { return (netbios_first_level_name_encode(name->name, name->scope, out, max_out)); } int smb_first_level_name_decode(unsigned char *in, struct name_entry *name) { return (netbios_first_level_name_decode((char *)in, (char *)name->name, (char *)name->scope)); } /* * smb_encode_netbios_name * * Set up the name and scope fields in the destination name_entry structure. * The name is padded with spaces to 15 bytes. The suffix is copied into the * last byte, i.e. "netbiosname ". The scope is copied and folded * to uppercase. */ void smb_encode_netbios_name(unsigned char *name, char suffix, unsigned char *scope, struct name_entry *dest) { smb_tonetbiosname((char *)name, (char *)dest->name, suffix); if (scope) { (void) strlcpy((char *)dest->scope, (const char *)scope, sizeof (dest->scope)); } else { (void) smb_config_getstr(SMB_CI_NBSCOPE, (char *)dest->scope, sizeof (dest->scope)); } (void) smb_strupr((char *)dest->scope); } void smb_init_name_struct(unsigned char *name, char suffix, unsigned char *scope, uint32_t ipaddr, unsigned short port, uint32_t attr, uint32_t addr_attr, struct name_entry *dest) { bzero(dest, sizeof (struct name_entry)); smb_encode_netbios_name(name, suffix, scope, dest); switch (smb_node_type) { case 'H': dest->attributes = attr | NAME_ATTR_OWNER_TYPE_HNODE; break; case 'M': dest->attributes = attr | NAME_ATTR_OWNER_TYPE_MNODE; break; case 'P': dest->attributes = attr | NAME_ATTR_OWNER_TYPE_PNODE; break; case 'B': default: dest->attributes = attr | NAME_ATTR_OWNER_TYPE_BNODE; break; } dest->addr_list.refresh_ttl = dest->addr_list.ttl = TO_SECONDS(DEFAULT_TTL); dest->addr_list.sin.sin_family = AF_INET; dest->addr_list.sinlen = sizeof (dest->addr_list.sin); dest->addr_list.sin.sin_addr.s_addr = ipaddr; dest->addr_list.sin.sin_port = port; dest->addr_list.attributes = addr_attr; dest->addr_list.forw = dest->addr_list.back = &dest->addr_list; } void smb_netbios_event(netbios_event_t event) { static char *event_msg[] = { "startup", "shutdown", "restart", "name service started", "name service stopped", "datagram service started", "datagram service stopped", "browser service started", "browser service stopped", "timer service started", "timer service stopped", "error", "dump" }; (void) mutex_lock(&nbtd.nbs_mtx); if (event == NETBIOS_EVENT_DUMP) { if (nbtd.nbs_last_event == NULL) nbtd.nbs_last_event = event_msg[event]; smb_netbios_dump(); (void) mutex_unlock(&nbtd.nbs_mtx); return; } nbtd.nbs_last_event = event_msg[event]; syslog(LOG_DEBUG, "netbios: %s", nbtd.nbs_last_event); switch (nbtd.nbs_state) { case NETBIOS_STATE_INIT: if (event == NETBIOS_EVENT_START) nbtd.nbs_state = NETBIOS_STATE_RUNNING; break; case NETBIOS_STATE_RUNNING: switch (event) { case NETBIOS_EVENT_NS_START: nbtd.nbs_ns.s_tid = pthread_self(); nbtd.nbs_ns.s_up = B_TRUE; break; case NETBIOS_EVENT_NS_STOP: nbtd.nbs_ns.s_up = B_FALSE; break; case NETBIOS_EVENT_DGM_START: nbtd.nbs_dgm.s_tid = pthread_self(); nbtd.nbs_dgm.s_up = B_TRUE; break; case NETBIOS_EVENT_DGM_STOP: nbtd.nbs_dgm.s_up = B_FALSE; break; case NETBIOS_EVENT_BROWSER_START: nbtd.nbs_browser.s_tid = pthread_self(); nbtd.nbs_browser.s_up = B_TRUE; break; case NETBIOS_EVENT_BROWSER_STOP: nbtd.nbs_browser.s_up = B_FALSE; break; case NETBIOS_EVENT_TIMER_START: nbtd.nbs_timer.s_tid = pthread_self(); nbtd.nbs_timer.s_up = B_TRUE; break; case NETBIOS_EVENT_TIMER_STOP: nbtd.nbs_timer.s_up = B_FALSE; break; case NETBIOS_EVENT_STOP: nbtd.nbs_state = NETBIOS_STATE_CLOSING; break; case NETBIOS_EVENT_ERROR: nbtd.nbs_state = NETBIOS_STATE_ERROR; ++nbtd.nbs_errors; break; default: break; } break; case NETBIOS_STATE_CLOSING: case NETBIOS_STATE_ERROR: default: switch (event) { case NETBIOS_EVENT_NS_STOP: nbtd.nbs_ns.s_up = B_FALSE; break; case NETBIOS_EVENT_DGM_STOP: nbtd.nbs_dgm.s_up = B_FALSE; break; case NETBIOS_EVENT_BROWSER_STOP: nbtd.nbs_browser.s_up = B_FALSE; break; case NETBIOS_EVENT_TIMER_STOP: nbtd.nbs_timer.s_up = B_FALSE; break; case NETBIOS_EVENT_STOP: nbtd.nbs_state = NETBIOS_STATE_CLOSING; break; case NETBIOS_EVENT_RESET: nbtd.nbs_state = NETBIOS_STATE_INIT; break; case NETBIOS_EVENT_ERROR: ++nbtd.nbs_errors; break; default: break; } break; } smb_netbios_dump(); (void) cond_broadcast(&nbtd.nbs_cv); (void) mutex_unlock(&nbtd.nbs_mtx); } void smb_netbios_wait(netbios_event_t event) { boolean_t *svc = NULL; boolean_t desired_state; (void) mutex_lock(&nbtd.nbs_mtx); switch (event) { case NETBIOS_EVENT_NS_START: case NETBIOS_EVENT_NS_STOP: svc = &nbtd.nbs_ns.s_up; desired_state = (event == NETBIOS_EVENT_NS_START) ? B_TRUE : B_FALSE; break; case NETBIOS_EVENT_DGM_START: case NETBIOS_EVENT_DGM_STOP: svc = &nbtd.nbs_dgm.s_up; desired_state = (event == NETBIOS_EVENT_DGM_START) ? B_TRUE : B_FALSE; break; case NETBIOS_EVENT_BROWSER_START: case NETBIOS_EVENT_BROWSER_STOP: svc = &nbtd.nbs_browser.s_up; desired_state = (event == NETBIOS_EVENT_BROWSER_START) ? B_TRUE : B_FALSE; break; default: (void) mutex_unlock(&nbtd.nbs_mtx); return; } while (*svc != desired_state) { if (nbtd.nbs_state != NETBIOS_STATE_RUNNING) break; (void) cond_wait(&nbtd.nbs_cv, &nbtd.nbs_mtx); } (void) mutex_unlock(&nbtd.nbs_mtx); } void smb_netbios_sleep(time_t seconds) { timestruc_t reltimeout; (void) mutex_lock(&nbtd.nbs_mtx); if (nbtd.nbs_state == NETBIOS_STATE_RUNNING) { if (seconds == 0) seconds = 1; reltimeout.tv_sec = seconds; reltimeout.tv_nsec = 0; (void) cond_reltimedwait(&nbtd.nbs_cv, &nbtd.nbs_mtx, &reltimeout); } (void) mutex_unlock(&nbtd.nbs_mtx); } boolean_t smb_netbios_running(void) { boolean_t is_running; (void) mutex_lock(&nbtd.nbs_mtx); if (nbtd.nbs_state == NETBIOS_STATE_RUNNING) is_running = B_TRUE; else is_running = B_FALSE; (void) mutex_unlock(&nbtd.nbs_mtx); return (is_running); } boolean_t smb_netbios_error(void) { boolean_t error; (void) mutex_lock(&nbtd.nbs_mtx); if (nbtd.nbs_state == NETBIOS_STATE_ERROR) error = B_TRUE; else error = B_FALSE; (void) mutex_unlock(&nbtd.nbs_mtx); return (error); } /* * Write the service state to /var/run/smb/netbios. * * This is a private interface. To update the file use: * smb_netbios_event(NETBIOS_EVENT_DUMP); */ static void smb_netbios_dump(void) { static struct { netbios_state_t state; char *text; } sm[] = { { NETBIOS_STATE_INIT, "init" }, { NETBIOS_STATE_RUNNING, "running" }, { NETBIOS_STATE_CLOSING, "closing" }, { NETBIOS_STATE_ERROR, "error" } }; char fname[MAXPATHLEN]; FILE *fp; struct passwd *pwd; struct group *grp; uid_t uid; gid_t gid; char *last_event = "none"; int i; (void) snprintf(fname, MAXPATHLEN, "%s/%s", SMB_VARRUN_DIR, SMB_NETBIOS_DUMP_FILE); if ((fp = fopen(fname, "w")) == NULL) return; pwd = getpwnam("root"); grp = getgrnam("sys"); uid = (pwd == NULL) ? 0 : pwd->pw_uid; gid = (grp == NULL) ? 3 : grp->gr_gid; (void) lockf(fileno(fp), F_LOCK, 0); (void) fchmod(fileno(fp), 0600); (void) fchown(fileno(fp), uid, gid); if (nbtd.nbs_last_event) last_event = nbtd.nbs_last_event; for (i = 0; i < sizeof (sm) / sizeof (sm[0]); ++i) { if (nbtd.nbs_state == sm[i].state) { (void) fprintf(fp, "State %s (event: %s, errors: %u)\n", sm[i].text, last_event, nbtd.nbs_errors); break; } } (void) fprintf(fp, "Name Service %-7s (%u)\n", nbtd.nbs_ns.s_up ? "up" : "down", nbtd.nbs_ns.s_tid); (void) fprintf(fp, "Datagram Service %-7s (%u)\n", nbtd.nbs_dgm.s_up ? "up" : "down", nbtd.nbs_dgm.s_tid); (void) fprintf(fp, "Browser Service %-7s (%u)\n", nbtd.nbs_browser.s_up ? "up" : "down", nbtd.nbs_browser.s_tid); (void) fprintf(fp, "Timer Service %-7s (%u)\n", nbtd.nbs_timer.s_up ? "up" : "down", nbtd.nbs_timer.s_tid); smb_netbios_cache_dump(fp); (void) lockf(fileno(fp), F_ULOCK, 0); (void) fclose(fp); } /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _SMB_NETBIOS_H_ #define _SMB_NETBIOS_H_ #include #include #include #include #include #include #include #include #define QUEUE_INSERT_TAIL(q, e) \ ((e)->back) = (void *)((q)->back); \ ((e)->forw) = (void *)(q); \ ((q)->back->forw) = (void *)(e); \ ((q)->back) = (void *)(e); #define QUEUE_CLIP(e) \ (e)->forw->back = (e)->back; \ (e)->back->forw = (e)->forw; \ (e)->forw = 0; \ (e)->back = 0; typedef enum { NETBIOS_EVENT_START = 0, NETBIOS_EVENT_STOP, NETBIOS_EVENT_RESET, NETBIOS_EVENT_NS_START, NETBIOS_EVENT_NS_STOP, NETBIOS_EVENT_DGM_START, NETBIOS_EVENT_DGM_STOP, NETBIOS_EVENT_BROWSER_START, NETBIOS_EVENT_BROWSER_STOP, NETBIOS_EVENT_TIMER_START, NETBIOS_EVENT_TIMER_STOP, NETBIOS_EVENT_ERROR, NETBIOS_EVENT_DUMP } netbios_event_t; typedef enum { NETBIOS_STATE_INIT = 0, NETBIOS_STATE_RUNNING, NETBIOS_STATE_CLOSING, NETBIOS_STATE_ERROR } netbios_state_t; typedef struct { pthread_t s_tid; boolean_t s_up; } netbios_svc_t; typedef struct { mutex_t nbs_mtx; cond_t nbs_cv; netbios_svc_t nbs_ns; netbios_svc_t nbs_dgm; netbios_svc_t nbs_browser; netbios_svc_t nbs_timer; netbios_state_t nbs_state; uint32_t nbs_errors; char *nbs_last_event; } netbios_service_t; extern char smb_node_type; #define SMB_NODETYPE_B 'B' #define SMB_NODETYPE_P 'P' #define SMB_NODETYPE_M 'M' #define SMB_NODETYPE_H 'H' /* * NAME service definitions */ #define ADDR_FLAG_INVALID 0x0000 #define ADDR_FLAG_VALID 0x0001 typedef struct addr_entry { struct addr_entry *forw; struct addr_entry *back; uint32_t attributes; uint32_t conflict_timer; uint32_t refresh_ttl; uint32_t ttl; struct sockaddr_in sin; int sinlen; uint32_t flags; } addr_entry_t; /* * The NODE_NAME ARRAY is an array of zero or more NUM_NAMES entries * of NODE_NAME records. Each NODE_NAME entry represents an active * name in the same NetBIOS scope as the requesting name in the * local name table of the responder. RR_NAME is the requesting * name. * * NODE_NAME Entry: * * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | * +--- ---+ * | | * +--- NETBIOS FORMAT NAME ---+ * | | * +--- ---+ * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | NAME_FLAGS | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * The NAME_FLAGS field: * * 1 1 1 1 1 1 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ * | G | ONT |DRG|CNF|ACT|PRM| RESERVED | * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ * * The NAME_FLAGS field is defined as: * * Symbol Bit(s) Description: * * RESERVED 7-15 Reserved for future use. Must be zero (0). * PRM 6 Permanent Name Flag. If one (1) then entry * is for the permanent node name. Flag is zero * (0) for all other names. * ACT 5 Active Name Flag. All entries have this flag * set to one (1). * CNF 4 Conflict Flag. If one (1) then name on this * node is in conflict. * DRG 3 Deregister Flag. If one (1) then this name * is in the process of being deleted. * ONT 1,2 Owner Node Type: * 00 = B node * 01 = P node * 10 = M node * 11 = Reserved for future use * G 0 Group Name Flag. * name. * If zero (0) then it is a UNIQUE NetBIOS name. */ typedef struct name_entry { struct name_entry *forw; struct name_entry *back; unsigned char name[NETBIOS_NAME_SZ]; unsigned char scope[NETBIOS_DOMAIN_NAME_MAX]; unsigned short attributes; struct addr_entry addr_list; mutex_t mtx; } name_entry_t; struct name_question { struct name_entry *name; unsigned question_type; unsigned question_class; }; struct resource_record { /* * These two flags and address are contained within RDATA * when rr_type==0x0020 (NB - NetBIOS general Name Service) * and rr_class==0x01 (IN - Internet Class). */ struct name_entry *name; unsigned short rr_type; unsigned short rr_class; uint32_t ttl; unsigned short rdlength; unsigned char *rdata; }; struct name_packet { unsigned short name_trn_id; unsigned short info; unsigned qdcount; /* question entries */ unsigned ancount; /* answer recs */ unsigned nscount; /* authority recs */ unsigned arcount; /* additional recs */ struct name_question *question; struct resource_record *answer; struct resource_record *authority; struct resource_record *additional; unsigned char block_data[4]; /* begining of space */ }; #define NAME_OPCODE_R 0x8000 /* RESPONSE flag: 1 bit */ #define NAME_OPCODE_OPCODE_MASK 0x7800 /* OPCODE Field: 4 bits */ #define NAME_OPCODE_QUERY 0x0000 #define NAME_OPCODE_REGISTRATION 0x2800 #define NAME_OPCODE_RELEASE 0x3000 #define NAME_OPCODE_WACK 0x3800 #define NAME_OPCODE_REFRESH 0x4000 #define NAME_OPCODE_MULTIHOME 0x7800 #define NAME_NM_FLAGS_AA 0x0400 /* Authoritative Answer:1 bit */ #define NAME_NM_FLAGS_TC 0x0200 /* Truncation: 1 bit */ #define NAME_NM_FLAGS_RD 0x0100 /* Recursion desired: 1 bit */ #define NAME_NM_FLAGS_RA 0x0080 /* Recursion available: 1 bit */ #define NAME_NM_FLAGS_x2 0x0040 /* reserved, mbz: 1 bit */ #define NAME_NM_FLAGS_x1 0x0020 /* reserved, mbz: 1 bit */ #define NAME_NM_FLAGS_B 0x0010 /* Broadcast: 1 bit */ #define NAME_RCODE_MASK 0x000f /* RCODE Field: 4 bits */ #define RCODE_FMT_ERR 0x0001 #define RCODE_SRV_ERR 0x0002 #define RCODE_NAM_ERR 0x0003 #define RCODE_IMP_ERR 0x0004 #define RCODE_RFS_ERR 0x0005 #define RCODE_ACT_ERR 0x0006 #define RCODE_CFT_ERR 0x0007 #define NM_FLAGS_UNICAST 0 #define NM_FLAGS_BROADCAST NAME_NM_FLAGS_B #define PACKET_TYPE(x) ((x) & (NAME_OPCODE_R | NAME_OPCODE_OPCODE_MASK | \ NAME_NM_FLAGS_AA | NAME_NM_FLAGS_RD)) #define RCODE(x) ((x) & NAME_RCODE_MASK) #define POSITIVE_RESPONSE(x) (RCODE(x) == 0) #define NEGATIVE_RESPONSE(x) (RCODE(x) != 0) #define END_NODE_CHALLENGE_REGISTRATION_REQUEST \ (NAME_OPCODE_REGISTRATION | NAME_NM_FLAGS_AA | NAME_NM_FLAGS_RD) #define END_NODE_CHALLENGE_NAME_REGISTRATION_RESPONSE \ (NAME_OPCODE_R | END_NODE_CHALLENGE_REGISTRATION_REQUEST) #define NAME_QUERY_REQUEST \ (NAME_OPCODE_QUERY | NAME_NM_FLAGS_RD) #define NAME_QUERY_RESPONSE \ (NAME_OPCODE_R | NAME_QUERY_REQUEST | \ NAME_NM_FLAGS_AA | NAME_NM_FLAGS_RD) #define NODE_STATUS_REQUEST \ (NAME_OPCODE_QUERY) #define NODE_STATUS_RESPONSE \ (NAME_OPCODE_R | NODE_STATUS_REQUEST | NAME_NM_FLAGS_AA) #define REDIRECT_NAME_QUERY_RESPONSE \ (NAME_OPCODE_R | NAME_QUERY_REQUEST | NAME_NM_FLAGS_RD) #define NAME_REFRESH_REQUEST \ (NAME_OPCODE_REFRESH) #define NAME_REGISTRATION_REQUEST \ (NAME_OPCODE_REGISTRATION | NAME_NM_FLAGS_RD) #define NAME_MULTIHOME_REGISTRATION_REQUEST \ (NAME_OPCODE_MULTIHOME | NAME_NM_FLAGS_RD) #define NAME_REGISTRATION_RESPONSE \ (NAME_OPCODE_R | NAME_REGISTRATION_REQUEST | NAME_NM_FLAGS_AA) #define NAME_RELEASE_REQUEST \ (NAME_OPCODE_RELEASE) #define NAME_RELEASE_RESPONSE \ (NAME_OPCODE_R | NAME_RELEASE_REQUEST | NAME_NM_FLAGS_AA) #define WACK_RESPONSE \ (NAME_OPCODE_R | NAME_OPCODE_WACK | NAME_NM_FLAGS_AA) #define NAME_QUESTION_TYPE_NB 0x0020 #define NAME_QUESTION_TYPE_NBSTAT 0x0021 #define NAME_QUESTION_CLASS_IN 0x0001 #define NAME_RR_TYPE_A 0x0001 /* IP Address */ #define NAME_RR_TYPE_NS 0x0002 /* Name Server */ #define NAME_RR_TYPE_NULL 0x000A /* NULL */ #define NAME_RR_TYPE_NB 0x0020 /* NetBIOS Name Service */ #define NAME_RR_TYPE_NBSTAT 0x0021 /* NetBIOS Node Status */ #define NAME_RR_CLASS_IN 0x0001 /* NetBIOS Node Status */ #define NAME_NB_FLAGS_ONT_MASK (3<<13) #define NAME_NB_FLAGS_ONT_B (0<<13) /* B-node (broadcast) */ #define NAME_NB_FLAGS_ONT_P (1<<13) /* P-node (point-to-point) */ #define NAME_NB_FLAGS_ONT_M (2<<13) /* M-node (multicast) */ #define NAME_NB_FLAGS_ONT_resv (3<<13) #define NAME_NB_FLAGS_G (1<<15) /* Group Name */ #define UNICAST 0 #define BROADCAST 1 #define POINTCAST 2 #define NAME_ATTR_UNIQUE 0x0000 #define NAME_ATTR_GROUP 0x8000 #define NAME_ATTR_OWNER_NODE_TYPE 0x6000 #define NAME_ATTR_OWNER_TYPE_BNODE 0x0000 #define NAME_ATTR_OWNER_TYPE_PNODE 0x2000 #define NAME_ATTR_OWNER_TYPE_MNODE 0x4000 #define NAME_ATTR_OWNER_TYPE_HNODE 0x6000 #define NAME_ATTR_DEREGISTER 0x1000 #define NAME_ATTR_CONFLICT 0x0800 #define NAME_ATTR_ACTIVE_NAME 0x0400 #define NAME_ATTR_PERMANENT 0x0200 #define NAME_ATTR_RESERVED 0x01FF #define NAME_ATTR_LOCAL 0x0001 #define NODE_TYPE(x) ((x) & NAME_ATTR_OWNER_NODE_TYPE)) #define IS_BNODE(x) (NODE_TYPE(x) == NAME_ATTR_OWNER_TYPE_BNODE) #define IS_PNODE(x) (NODE_TYPE(x) == NAME_ATTR_OWNER_TYPE_PNODE) #define IS_MNODE(x) (NODE_TYPE(x) == NAME_ATTR_OWNER_TYPE_MNODE) #define IS_HNODE(x) (NODE_TYPE(x) == NAME_ATTR_OWNER_TYPE_HNODE) #define IS_UNIQUE(x) (((x) & NAME_ATTR_GROUP) == 0) #define IS_GROUP(x) (((x) & NAME_ATTR_GROUP) != 0) #define IS_PERMANENT(x) (((x) & NAME_ATTR_PERMANENT) != 0) #define IS_CONFLICTING(x) (((x) & NAME_ATTR_CONFLICT) != 0) #define IS_ACTIVE(x) (((x) & NAME_ATTR_ACTIVE) != 0) #define IS_DEGREGISTERED(x) (((x) & NAME_ATTR_ACTIVE) != 0) #define IS_LOCAL(x) (((x) & NAME_ATTR_LOCAL) != 0) #define IS_PUBLIC(x) (((x) & NAME_ATTR_LOCAL) == 0) #define PUBLIC_BITS(x) ((x) & ~NAME_ATTR_RESERVED) #define SAME_SCOPE(scope, e) (strcmp((scope), ((e)->scope)) == 0) /* * STATISTICS Field of the NODE STATUS RESPONSE: * * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | UNIT_ID (Unique unit ID) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | UNIT_ID,continued | JUMPERS | TEST_RESULT | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | VERSION_NUMBER | PERIOD_OF_STATISTICS | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | NUMBER_OF_CRCs | NUMBER_ALIGNMENT_ERRORS | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | NUMBER_OF_COLLISIONS | NUMBER_SEND_ABORTS | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | NUMBER_GOOD_SENDS | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | NUMBER_GOOD_RECEIVES | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | NUMBER_RETRANSMITS | NUMBER_NO_RESOURCE_CONDITIONS | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | NUMBER_FREE_COMMAND_BLOCKS | TOTAL_NUMBER_COMMAND_BLOCKS | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * |MAX_TOTAL_NUMBER_COMMAND_BLOCKS| NUMBER_PENDING_SESSIONS | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | MAX_NUMBER_PENDING_SESSIONS | MAX_TOTAL_SESSIONS_POSSIBLE | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | SESSION_DATA_PACKET_SIZE | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ typedef struct { unsigned char unit_id[6]; unsigned char jumpers; unsigned char test_result; unsigned short version_number; unsigned short statistical_period; unsigned short crc_errors; unsigned short alignment_errors; unsigned short collisions; unsigned short send_aborts; unsigned int good_sends; unsigned int good_receives; unsigned short retransmits; unsigned short no_resource_conditions; unsigned short free_command_blocks; unsigned short total_command_blocks; unsigned short max_total_command_blocks; unsigned short pending_sessions; unsigned short max_pending_sessions; unsigned short total_possible_sessions; unsigned short session_data_packet_size; } node_status_response; /* * 4.4.1. NetBIOS DATAGRAM HEADER * * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | MSG_TYPE | FLAGS | DGM_ID | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | SOURCE_IP | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | SOURCE_PORT | DGM_LENGTH | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | PACKET_OFFSET | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ typedef struct { unsigned char msg_type; unsigned char flags; unsigned short dgm_id; uint32_t source_ip; unsigned short source_port; unsigned short dgm_length; unsigned short packet_offset; } datagram_header; /* * MSG_TYPE values (in hexidecimal): * * 10 - DIRECT_UNIQUE DATAGRAM * 11 - DIRECT_GROUP DATAGRAM * 12 - BROADCAST DATAGRAM * 13 - DATAGRAM ERROR * 14 - DATAGRAM QUERY REQUEST * 15 - DATAGRAM POSITIVE QUERY RESPONSE * 16 - DATAGRAM NEGATIVE QUERY RESPONSE */ #define DATAGRAM_TYPE_DIRECT_UNIQUE 0x10 #define DATAGRAM_TYPE_DIRECT_GROUP 0x11 #define DATAGRAM_TYPE_BROADCAST 0x12 #define DATAGRAM_TYPE_ERROR_DATAGRAM 0x13 #define DATAGRAM_TYPE_QUERY_REQUEST 0x14 #define DATAGRAM_TYPE_POSITIVE_RESPONSE 0x15 #define DATAGRAM_TYPE_NEGATIVE_RESPONSE 0x16 /* * Bit definitions of the FLAGS field: * * 0 1 2 3 4 5 6 7 * +---+---+---+---+---+---+---+---+ * | 0 | 0 | 0 | 0 | SNT | F | M | * +---+---+---+---+---+---+---+---+ * * Symbol Bit(s) Description * * M 7 MORE flag, If set then more NetBIOS datagram * fragments follow. * * F 6 FIRST packet flag, If set then this is first * (and possibly only) fragment of NetBIOS * datagram * * SNT 4,5 Source End-Node type: * 00 = B node * 01 = P node * 10 = M node * 11 = H node * RESERVED 0-3 Reserved, must be zero (0) */ #define DATAGRAM_FLAGS_MORE 0x01 #define DATAGRAM_FLAGS_FIRST 0x02 #define DATAGRAM_FLAGS_SRC_TYPE 0x0c #define DATAGRAM_FLAGS_B_NODE 0x00 #define DATAGRAM_FLAGS_P_NODE 0x04 #define DATAGRAM_FLAGS_M_NODE 0x08 #define DATAGRAM_FLAGS_H_NODE 0x0C #define DATAGRAM_FLAGS_NBDD 0x0c #define DATAGRAM_FLAGS_RESERVED 0xf0 /* * 4.4.2. DIRECT_UNIQUE, DIRECT_GROUP, & BROADCAST DATAGRAM * * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | MSG_TYPE | FLAGS | DGM_ID | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | SOURCE_IP | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | SOURCE_PORT | DGM_LENGTH | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | PACKET_OFFSET | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | * | | * / SOURCE_NAME / * / / * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | * / DESTINATION_NAME / * / / * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | * / USER_DATA / * / / * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ typedef struct { datagram_header header; unsigned char *source_name; unsigned char *destination_name; unsigned char *user_data; } datagram_packet; /* * 4.4.3. DATAGRAM ERROR PACKET * * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | MSG_TYPE | FLAGS | DGM_ID | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | SOURCE_IP | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | SOURCE_PORT | ERROR_CODE | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * ERROR_CODE values (in hexidecimal): * * 82 - DESTINATION NAME NOT PRESENT * 83 - INVALID SOURCE NAME FORMAT * 84 - INVALID DESTINATION NAME FORMAT */ typedef struct { unsigned char msg_type; unsigned char flags; unsigned short dgm_id; uint32_t source_ip; unsigned short source_port; unsigned char error; } datagram_error_packet; /* * 4.4.4. DATAGRAM QUERY REQUEST * * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | MSG_TYPE | FLAGS | DGM_ID | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | SOURCE_IP | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | SOURCE_PORT | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | * / DESTINATION_NAME / * / / * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * 4.4.5. DATAGRAM POSITIVE AND NEGATIVE QUERY RESPONSE * * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | MSG_TYPE | FLAGS | DGM_ID | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | SOURCE_IP | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | SOURCE_PORT | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | * / DESTINATION_NAME / * / / * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ typedef struct datagram_query_packet { unsigned char msg_type; unsigned char flags; unsigned short dgm_id; uint32_t source_ip; unsigned short source_port; unsigned char destination_name[MAX_NAME_LENGTH]; } datagram_query_packet; typedef struct datagram { struct datagram *forw; struct datagram *back; struct addr_entry inaddr; int discard_timer; unsigned char packet_type; unsigned char flags; unsigned short datagram_id; struct name_entry src; struct name_entry dest; unsigned short offset; unsigned short data_length; unsigned char *data; unsigned int rawbytes; unsigned char rawbuf[MAX_DATAGRAM_LENGTH]; } datagram; typedef struct datagram_queue { struct datagram *forw; struct datagram *back; } datagram_queue; typedef struct name_queue { struct name_entry head; mutex_t mtx; } name_queue_t; typedef struct nbcache_iter { HT_ITERATOR nbc_hti; struct name_entry *nbc_entry; } nbcache_iter_t; #define NETBIOS_EMPTY_NAME (unsigned char *)"" #define NETBIOS_NAME_IS_STAR(name) \ (bcmp(name, "*\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", NETBIOS_NAME_SZ) == 0) /* * NetBIOS service state machine interface */ void smb_netbios_event(netbios_event_t); void smb_netbios_wait(netbios_event_t); void smb_netbios_sleep(time_t); boolean_t smb_netbios_running(void); boolean_t smb_netbios_error(void); /* * Name Cache Functions */ int smb_netbios_cache_init(void); void smb_netbios_cache_fini(void); void smb_netbios_cache_dump(FILE *fp); int smb_netbios_cache_count(void); void smb_netbios_cache_clean(void); void smb_netbios_cache_reset_ttl(void); void smb_netbios_cache_delete_locals(name_queue_t *); void smb_netbios_cache_refresh(name_queue_t *); int smb_netbios_cache_insert(struct name_entry *name); int smb_netbios_cache_insert_list(struct name_entry *name); void smb_netbios_cache_delete(struct name_entry *name); int smb_netbios_cache_delete_addr(struct name_entry *name); struct name_entry *smb_netbios_cache_lookup(struct name_entry *name); struct name_entry *smb_netbios_cache_lookup_addr(struct name_entry *name); void smb_netbios_cache_update_entry(struct name_entry *, struct name_entry *); void smb_netbios_cache_unlock_entry(struct name_entry *); unsigned char *smb_netbios_cache_status(unsigned char *, int, unsigned char *); int smb_netbios_cache_getfirst(nbcache_iter_t *); int smb_netbios_cache_getnext(nbcache_iter_t *); void smb_netbios_name_dump(FILE *fp, struct name_entry *entry); void smb_netbios_name_logf(struct name_entry *entry); void smb_netbios_name_freeaddrs(struct name_entry *entry); struct name_entry *smb_netbios_name_dup(struct name_entry *, int); /* Name service functions */ void *smb_netbios_name_service(void *); void smb_init_name_struct(unsigned char *, char, unsigned char *, uint32_t, unsigned short, uint32_t, uint32_t, struct name_entry *); struct name_entry *smb_name_find_name(struct name_entry *name); int smb_name_add_name(struct name_entry *name); int smb_name_delete_name(struct name_entry *name); void smb_name_unlock_name(struct name_entry *name); void smb_netbios_name_config(void); void smb_netbios_name_unconfig(void); void smb_netbios_name_tick(void); int smb_first_level_name_encode(struct name_entry *, unsigned char *, int); int smb_first_level_name_decode(unsigned char *, struct name_entry *); void smb_encode_netbios_name(unsigned char *, char, unsigned char *, struct name_entry *); /* Datagram service functions */ void *smb_netbios_datagram_service(void *); int smb_netbios_datagram_send(struct name_entry *, struct name_entry *, unsigned char *, int); void smb_netbios_datagram_tick(void); /* browser functions */ void *smb_browser_dispatch(void *arg); void *smb_browser_service(void *); int smb_browser_load_transact_header(unsigned char *, int, int, int, char *); /* Netlogon function */ void smb_netlogon_receive(struct datagram *, char *, unsigned char *, int); void smb_netlogon_request(struct name_entry *, char *); #endif /* _SMB_NETBIOS_H_ */ /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #define NETBIOS_HTAB_SZ 128 #define NETBIOS_HKEY_SZ (NETBIOS_NAME_SZ + NETBIOS_DOMAIN_NAME_MAX) #define NETBIOS_SAME_IP(addr1, addr2) \ ((addr1)->sin.sin_addr.s_addr == (addr2)->sin.sin_addr.s_addr) typedef char nb_key_t[NETBIOS_HKEY_SZ]; static HT_HANDLE *smb_netbios_cache = 0; static rwlock_t nb_cache_lock; static void smb_strname(struct name_entry *name, char *buf, int bufsize); static void hash_callback(HT_ITEM *item); static int smb_netbios_match(const char *key1, const char *key2, size_t n); static void smb_netbios_cache_key(char *key, unsigned char *name, unsigned char *scope); int smb_netbios_cache_init(void) { (void) rw_wrlock(&nb_cache_lock); if (smb_netbios_cache == NULL) { smb_netbios_cache = ht_create_table(NETBIOS_HTAB_SZ, NETBIOS_HKEY_SZ, HTHF_FIXED_KEY); if (smb_netbios_cache == NULL) { syslog(LOG_ERR, "nbns: cannot create name cache"); (void) rw_unlock(&nb_cache_lock); return (-1); } (void) ht_register_callback(smb_netbios_cache, hash_callback); ht_set_cmpfn(smb_netbios_cache, smb_netbios_match); } (void) rw_unlock(&nb_cache_lock); return (0); } void smb_netbios_cache_fini(void) { (void) rw_wrlock(&nb_cache_lock); ht_destroy_table(smb_netbios_cache); smb_netbios_cache = NULL; (void) rw_unlock(&nb_cache_lock); } void smb_netbios_cache_clean(void) { (void) rw_wrlock(&nb_cache_lock); (void) ht_clean_table(smb_netbios_cache); (void) rw_unlock(&nb_cache_lock); } int smb_netbios_cache_getfirst(nbcache_iter_t *iter) { HT_ITEM *item; struct name_entry *entry; (void) rw_rdlock(&nb_cache_lock); item = ht_findfirst(smb_netbios_cache, &iter->nbc_hti); if (item == NULL || item->hi_data == NULL) { (void) rw_unlock(&nb_cache_lock); return (-1); } entry = (struct name_entry *)item->hi_data; (void) mutex_lock(&entry->mtx); iter->nbc_entry = smb_netbios_name_dup(entry, 1); (void) mutex_unlock(&entry->mtx); (void) rw_unlock(&nb_cache_lock); return ((iter->nbc_entry) ? 0 : -1); } int smb_netbios_cache_getnext(nbcache_iter_t *iter) { HT_ITEM *item; struct name_entry *entry; (void) rw_rdlock(&nb_cache_lock); item = ht_findnext(&iter->nbc_hti); if (item == NULL || item->hi_data == NULL) { (void) rw_unlock(&nb_cache_lock); return (-1); } entry = (struct name_entry *)item->hi_data; (void) mutex_lock(&entry->mtx); iter->nbc_entry = smb_netbios_name_dup(entry, 1); (void) mutex_unlock(&entry->mtx); (void) rw_unlock(&nb_cache_lock); return ((iter->nbc_entry) ? 0 : -1); } /* * smb_netbios_cache_lookup * * Searches the name cache for the given entry, if found * the entry will be locked before returning to caller * so caller MUST unlock the entry after it's done with it. */ struct name_entry * smb_netbios_cache_lookup(struct name_entry *name) { HT_ITEM *item; nb_key_t key; struct name_entry *entry = NULL; unsigned char hostname[MAXHOSTNAMELEN]; if (NETBIOS_NAME_IS_STAR(name->name)) { /* Return our address */ if (smb_getnetbiosname((char *)hostname, sizeof (hostname)) != 0) return (NULL); smb_encode_netbios_name(hostname, 0x00, NULL, name); } (void) rw_rdlock(&nb_cache_lock); smb_netbios_cache_key(key, name->name, name->scope); item = ht_find_item(smb_netbios_cache, key); if (item) { entry = (struct name_entry *)item->hi_data; (void) mutex_lock(&entry->mtx); if ((entry->attributes & NAME_ATTR_CONFLICT) != 0) { (void) mutex_unlock(&entry->mtx); entry = NULL; } } (void) rw_unlock(&nb_cache_lock); return (entry); } void smb_netbios_cache_unlock_entry(struct name_entry *name) { if (name) (void) mutex_unlock(&name->mtx); } /* * smb_netbios_cache_lookup_addr * * lookup the given 'name' in the cache and then checks * if the address also matches with the found entry. * 'name' is supposed to contain only one address. * * The found entry will be locked before returning to caller * so caller MUST unlock the entry after it's done with it. */ struct name_entry * smb_netbios_cache_lookup_addr(struct name_entry *name) { struct name_entry *entry = 0; addr_entry_t *addr; addr_entry_t *name_addr; HT_ITEM *item; nb_key_t key; (void) rw_rdlock(&nb_cache_lock); smb_netbios_cache_key(key, name->name, name->scope); item = ht_find_item(smb_netbios_cache, key); if (item && item->hi_data) { name_addr = &name->addr_list; entry = (struct name_entry *)item->hi_data; (void) mutex_lock(&entry->mtx); addr = &entry->addr_list; do { if (NETBIOS_SAME_IP(addr, name_addr)) { /* note that entry lock isn't released here */ (void) rw_unlock(&nb_cache_lock); return (entry); } addr = addr->forw; } while (addr != &entry->addr_list); (void) mutex_unlock(&entry->mtx); } (void) rw_unlock(&nb_cache_lock); return (0); } int smb_netbios_cache_insert(struct name_entry *name) { struct name_entry *entry; addr_entry_t *addr; addr_entry_t *name_addr; HT_ITEM *item; nb_key_t key; int rc; /* No point in adding a name with IP address 255.255.255.255 */ if (name->addr_list.sin.sin_addr.s_addr == 0xffffffff) return (0); (void) rw_wrlock(&nb_cache_lock); smb_netbios_cache_key(key, name->name, name->scope); item = ht_find_item(smb_netbios_cache, key); if (item && item->hi_data) { /* Name already exists */ entry = (struct name_entry *)item->hi_data; (void) mutex_lock(&entry->mtx); name_addr = &name->addr_list; addr = &entry->addr_list; if (NETBIOS_SAME_IP(addr, name_addr) && (addr->sin.sin_port == name_addr->sin.sin_port)) { entry->attributes |= name_addr->attributes & NAME_ATTR_LOCAL; (void) mutex_unlock(&entry->mtx); (void) rw_unlock(&nb_cache_lock); return (0); } /* Was not primary: looks for others */ for (addr = entry->addr_list.forw; addr != &entry->addr_list; addr = addr->forw) { if (NETBIOS_SAME_IP(addr, name_addr) && (addr->sin.sin_port == name_addr->sin.sin_port)) { (void) mutex_unlock(&entry->mtx); (void) rw_unlock(&nb_cache_lock); return (0); } } if ((addr = malloc(sizeof (addr_entry_t))) != NULL) { *addr = name->addr_list; entry->attributes |= addr->attributes; QUEUE_INSERT_TAIL(&entry->addr_list, addr); rc = 0; } else { rc = -1; } (void) mutex_unlock(&entry->mtx); (void) rw_unlock(&nb_cache_lock); return (rc); } if ((entry = malloc(sizeof (struct name_entry))) == NULL) { (void) rw_unlock(&nb_cache_lock); return (-1); } *entry = *name; entry->addr_list.forw = entry->addr_list.back = &entry->addr_list; entry->attributes |= entry->addr_list.attributes; (void) mutex_init(&entry->mtx, 0, 0); if (ht_replace_item(smb_netbios_cache, key, entry) == 0) { free(entry); (void) rw_unlock(&nb_cache_lock); return (-1); } (void) rw_unlock(&nb_cache_lock); return (0); } void smb_netbios_cache_delete(struct name_entry *name) { nb_key_t key; HT_ITEM *item; struct name_entry *entry; (void) rw_wrlock(&nb_cache_lock); smb_netbios_cache_key(key, name->name, name->scope); item = ht_find_item(smb_netbios_cache, key); if (item && item->hi_data) { entry = (struct name_entry *)item->hi_data; (void) mutex_lock(&entry->mtx); ht_mark_delete(smb_netbios_cache, item); (void) mutex_unlock(&entry->mtx); } (void) rw_unlock(&nb_cache_lock); } /* * smb_netbios_cache_insert_list * * Insert a name with multiple addresses */ int smb_netbios_cache_insert_list(struct name_entry *name) { struct name_entry entry; addr_entry_t *addr; addr = &name->addr_list; do { smb_init_name_struct(NETBIOS_EMPTY_NAME, 0, name->scope, addr->sin.sin_addr.s_addr, addr->sin.sin_port, name->attributes, addr->attributes, &entry); (void) memcpy(entry.name, name->name, NETBIOS_NAME_SZ); entry.addr_list.refresh_ttl = entry.addr_list.ttl = addr->refresh_ttl; (void) smb_netbios_cache_insert(&entry); addr = addr->forw; } while (addr != &name->addr_list); return (0); } void smb_netbios_cache_update_entry(struct name_entry *entry, struct name_entry *name) { addr_entry_t *addr; addr_entry_t *name_addr; addr = &entry->addr_list; name_addr = &name->addr_list; if (IS_UNIQUE(entry->attributes)) { do { addr->ttl = name_addr->ttl; addr = addr->forw; } while (addr != &entry->addr_list); } else { do { if (NETBIOS_SAME_IP(addr, name_addr) && (addr->sin.sin_port == name_addr->sin.sin_port)) { addr->ttl = name_addr->ttl; return; } addr = addr->forw; } while (addr != &entry->addr_list); } } /* * smb_netbios_cache_status * * Scan the name cache and gather status for * Node Status response for names in the given scope */ unsigned char * smb_netbios_cache_status(unsigned char *buf, int bufsize, unsigned char *scope) { HT_ITERATOR hti; HT_ITEM *item; struct name_entry *name; unsigned char *numnames; unsigned char *scan; unsigned char *scan_end; scan = buf; scan_end = scan + bufsize; numnames = scan++; *numnames = 0; (void) rw_rdlock(&nb_cache_lock); item = ht_findfirst(smb_netbios_cache, &hti); do { if (item == 0) break; if (item->hi_data == 0) continue; if ((scan + NETBIOS_NAME_SZ + 2) >= scan_end) /* no room for adding next entry */ break; name = (struct name_entry *)item->hi_data; (void) mutex_lock(&name->mtx); if (IS_LOCAL(name->attributes) && (strcasecmp((char *)scope, (char *)name->scope) == 0)) { bcopy(name->name, scan, NETBIOS_NAME_SZ); scan += NETBIOS_NAME_SZ; *scan++ = (PUBLIC_BITS(name->attributes) >> 8) & 0xff; *scan++ = PUBLIC_BITS(name->attributes) & 0xff; (*numnames)++; } (void) mutex_unlock(&name->mtx); } while ((item = ht_findnext(&hti)) != 0); (void) rw_unlock(&nb_cache_lock); return (scan); } void smb_netbios_cache_reset_ttl() { addr_entry_t *addr; struct name_entry *name; HT_ITERATOR hti; HT_ITEM *item; (void) rw_rdlock(&nb_cache_lock); item = ht_findfirst(smb_netbios_cache, &hti); do { if (item == 0) break; if (item->hi_data == 0) continue; name = (struct name_entry *)item->hi_data; (void) mutex_lock(&name->mtx); addr = &name->addr_list; do { if (addr->ttl < 1) { if (addr->refresh_ttl) addr->ttl = addr->refresh_ttl; else addr->refresh_ttl = addr->ttl = TO_SECONDS(DEFAULT_TTL); } addr = addr->forw; } while (addr != &name->addr_list); (void) mutex_unlock(&name->mtx); } while ((item = ht_findnext(&hti)) != 0); (void) rw_unlock(&nb_cache_lock); } /* * Returns TRUE when given name is added to the refresh queue * FALSE if not. */ static boolean_t smb_netbios_cache_insrefq(name_queue_t *refq, HT_ITEM *item) { struct name_entry *name; struct name_entry *refent; name = (struct name_entry *)item->hi_data; if (IS_LOCAL(name->attributes)) { if (IS_UNIQUE(name->attributes)) { refent = smb_netbios_name_dup(name, 1); if (refent) { QUEUE_INSERT_TAIL(&refq->head, refent) } /* next name */ return (B_TRUE); } } else { ht_mark_delete(smb_netbios_cache, item); refent = smb_netbios_name_dup(name, 0); if (refent) { QUEUE_INSERT_TAIL(&refq->head, refent) } /* next name */ return (B_TRUE); } return (B_FALSE); } /* * smb_netbios_cache_refresh * * Scans the name cache and add all local unique names * and non-local names the passed refresh queue. Non- * local names will also be marked as deleted. * * NOTE that the caller MUST protect the queue using * its mutex */ void smb_netbios_cache_refresh(name_queue_t *refq) { struct name_entry *name; addr_entry_t *addr; HT_ITERATOR hti; HT_ITEM *item; bzero(&refq->head, sizeof (refq->head)); refq->head.forw = refq->head.back = &refq->head; (void) rw_rdlock(&nb_cache_lock); item = ht_findfirst(smb_netbios_cache, &hti); do { /* name loop */ if (item == 0) break; if (item->hi_data == 0) continue; name = (struct name_entry *)item->hi_data; (void) mutex_lock(&name->mtx); addr = &name->addr_list; do { /* address loop */ if (addr->ttl > 0) { addr->ttl--; if (addr->ttl == 0) { if (smb_netbios_cache_insrefq(refq, item)) break; } } addr = addr->forw; } while (addr != &name->addr_list); (void) mutex_unlock(&name->mtx); } while ((item = ht_findnext(&hti)) != 0); (void) rw_unlock(&nb_cache_lock); } /* * smb_netbios_cache_delete_locals * * Scans the name cache and add all local names to * the passed delete queue. * * NOTE that the caller MUST protect the queue using * its mutex */ void smb_netbios_cache_delete_locals(name_queue_t *delq) { struct name_entry *entry; struct name_entry *delent; HT_ITERATOR hti; HT_ITEM *item; bzero(&delq->head, sizeof (delq->head)); delq->head.forw = delq->head.back = &delq->head; (void) rw_wrlock(&nb_cache_lock); item = ht_findfirst(smb_netbios_cache, &hti); do { if (item == 0) break; if (item->hi_data == 0) continue; entry = (struct name_entry *)item->hi_data; (void) mutex_lock(&entry->mtx); if (IS_LOCAL(entry->attributes)) { ht_mark_delete(smb_netbios_cache, item); delent = smb_netbios_name_dup(entry, 1); if (delent) { QUEUE_INSERT_TAIL(&delq->head, delent) } } (void) mutex_unlock(&entry->mtx); } while ((item = ht_findnext(&hti)) != 0); (void) rw_unlock(&nb_cache_lock); } void smb_netbios_name_freeaddrs(struct name_entry *entry) { addr_entry_t *addr; if (entry == 0) return; while ((addr = entry->addr_list.forw) != &entry->addr_list) { QUEUE_CLIP(addr); free(addr); } } /* * smb_netbios_cache_count * * Returns the number of names in the cache */ int smb_netbios_cache_count() { int cnt; (void) rw_rdlock(&nb_cache_lock); cnt = ht_get_total_items(smb_netbios_cache); (void) rw_unlock(&nb_cache_lock); return (cnt); } void smb_netbios_cache_dump(FILE *fp) { struct name_entry *name; HT_ITERATOR hti; HT_ITEM *item; (void) rw_rdlock(&nb_cache_lock); if (ht_get_total_items(smb_netbios_cache) != 0) { (void) fprintf(fp, "\n%-22s %-16s %-16s %s\n", "Name", "Type", "Address", "TTL"); (void) fprintf(fp, "%s%s\n", "-------------------------------", "------------------------------"); } item = ht_findfirst(smb_netbios_cache, &hti); while (item) { if (item->hi_data) { name = (struct name_entry *)item->hi_data; (void) mutex_lock(&name->mtx); smb_netbios_name_dump(fp, name); (void) mutex_unlock(&name->mtx); } item = ht_findnext(&hti); } (void) rw_unlock(&nb_cache_lock); } void smb_netbios_name_dump(FILE *fp, struct name_entry *entry) { char buf[MAXHOSTNAMELEN]; addr_entry_t *addr; char *type; int count = 0; smb_strname(entry, buf, sizeof (buf)); type = (IS_UNIQUE(entry->attributes)) ? "UNIQUE" : "GROUP"; (void) fprintf(fp, "%s %-6s (0x%04x) ", buf, type, entry->attributes); addr = &entry->addr_list; do { if (count == 0) (void) fprintf(fp, "%-16s %d\n", inet_ntoa(addr->sin.sin_addr), addr->ttl); else (void) fprintf(fp, "%-28s (0x%04x) %-16s %d\n", " ", addr->attributes, inet_ntoa(addr->sin.sin_addr), addr->ttl); ++count; addr = addr->forw; } while (addr != &entry->addr_list); } void smb_netbios_name_logf(struct name_entry *entry) { char namebuf[MAXHOSTNAMELEN]; addr_entry_t *addr; smb_strname(entry, namebuf, sizeof (namebuf)); syslog(LOG_DEBUG, "%s flags=0x%x\n", namebuf, entry->attributes); addr = &entry->addr_list; do { syslog(LOG_DEBUG, " %s ttl=%d flags=0x%x port=%d", inet_ntoa(addr->sin.sin_addr), addr->ttl, addr->attributes, addr->sin.sin_port); addr = addr->forw; } while (addr && (addr != &entry->addr_list)); } /* * smb_netbios_name_dup * * Duplicate the given name entry. If 'alladdr' is 0 only * copy the primary address otherwise duplicate all the * addresses. NOTE that the duplicate structure is not * like a regular cache entry i.e. it's a contiguous block * of memory and each addr structure doesn't have it's own * allocated memory. So, the returned structure can be freed * by one free call. */ struct name_entry * smb_netbios_name_dup(struct name_entry *entry, int alladdr) { addr_entry_t *addr; addr_entry_t *dup_addr; struct name_entry *dup; int addr_cnt = 0; int size = 0; if (alladdr) { addr = entry->addr_list.forw; while (addr && (addr != &entry->addr_list)) { addr_cnt++; addr = addr->forw; } } size = sizeof (struct name_entry) + (addr_cnt * sizeof (addr_entry_t)); dup = (struct name_entry *)malloc(size); if (dup == 0) return (0); bzero(dup, size); dup->forw = dup->back = dup; dup->attributes = entry->attributes; (void) memcpy(dup->name, entry->name, NETBIOS_NAME_SZ); (void) strlcpy((char *)dup->scope, (char *)entry->scope, NETBIOS_DOMAIN_NAME_MAX); dup->addr_list = entry->addr_list; dup->addr_list.forw = dup->addr_list.back = &dup->addr_list; if (alladdr == 0) return (dup); /* LINTED - E_BAD_PTR_CAST_ALIGN */ dup_addr = (addr_entry_t *)((unsigned char *)dup + sizeof (struct name_entry)); addr = entry->addr_list.forw; while (addr && (addr != &entry->addr_list)) { *dup_addr = *addr; QUEUE_INSERT_TAIL(&dup->addr_list, dup_addr); addr = addr->forw; dup_addr++; } return (dup); } static void smb_strname(struct name_entry *entry, char *buf, int bufsize) { char tmp[MAXHOSTNAMELEN]; char *p; (void) snprintf(tmp, MAXHOSTNAMELEN, "%15.15s", entry->name); if ((p = strchr(tmp, ' ')) != NULL) *p = '\0'; if (entry->scope[0] != '\0') { (void) strlcat(tmp, ".", MAXHOSTNAMELEN); (void) strlcat(tmp, (char *)entry->scope, MAXHOSTNAMELEN); } (void) snprintf(buf, bufsize, "%-16s <%02X>", tmp, entry->name[15]); } static void hash_callback(HT_ITEM *item) { struct name_entry *entry; if (item && item->hi_data) { entry = (struct name_entry *)item->hi_data; smb_netbios_name_freeaddrs(entry); free(entry); } } /*ARGSUSED*/ static int smb_netbios_match(const char *key1, const char *key2, size_t n) { int res; res = bcmp(key1, key2, NETBIOS_NAME_SZ); if (res == 0) { /* Names are the same, compare scopes */ res = strcmp(key1 + NETBIOS_NAME_SZ, key2 + NETBIOS_NAME_SZ); } return (res); } static void smb_netbios_cache_key(char *key, unsigned char *name, unsigned char *scope) { bzero(key, NETBIOS_HKEY_SZ); (void) memcpy(key, name, NETBIOS_NAME_SZ); (void) memcpy(key + NETBIOS_NAME_SZ, scope, strlen((const char *)scope)); } /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Description: * * Contains base code for netbios datagram service. * * Relavent sections from RFC1002: * * 5.3. NetBIOS DATAGRAM SERVICE PROTOCOLS * * The following are GLOBAL variables and should be NetBIOS user * configurable: * * - SCOPE_ID: the non-leaf section of the domain name preceded by a * '.' which represents the domain of the NetBIOS scope for the * NetBIOS name. The following protocol description only supports * single scope operation. * * - MAX_DATAGRAM_LENGTH: the maximum length of an IP datagram. The * minimal maximum length defined in for IP is 576 bytes. This * value is used when determining whether to fragment a NetBIOS * datagram. Implementations are expected to be capable of * receiving unfragmented NetBIOS datagrams up to their maximum * size. * * - BROADCAST_ADDRESS: the IP address B-nodes use to send datagrams * with group name destinations and broadcast datagrams. The * default is the IP broadcast address for a single IP network. * * * The following are Defined Constants for the NetBIOS Datagram * Service: * * - IPPORT_NETBIOS_DGM: the globally well-known UDP port allocated * where the NetBIOS Datagram Service receives UDP packets. See * section 6, "Defined Constants", for its value. */ /* * * 6. DEFINED CONSTANTS AND VARIABLES * * GENERAL: * * SCOPE_ID The name of the NetBIOS scope. * * This is expressed as a character * string meeting the requirements of * the domain name system and without * a leading or trailing "dot". * * An implementation may elect to make * this a single global value for the * node or allow it to be specified * with each separate NetBIOS name * (thus permitting cross-scope * references.) * * BROADCAST_ADDRESS An IP address composed of the * node network and subnetwork * numbers with all remaining bits set * to one. * * I.e. "Specific subnet" broadcast * addressing according to section 2.3 * of RFC 950. * * BCAST_REQ_RETRY_TIMEOUT 250 milliseconds. * An adaptive timer may be used. * * BCAST_REQ_RETRY_COUNT 3 * * UCAST_REQ_RETRY_TIMEOUT 5 seconds * An adaptive timer may be used. * * UCAST_REQ_RETRY_COUNT 3 * * MAX_DATAGRAM_LENGTH 576 bytes (default) * * DATAGRAM SERVICE: * * IPPORT_NETBIOS_DGM 138 (decimal) * * FRAGMENT_TO 2 seconds (default) */ #include #include #include #include #include #include #include #include #include #include #include static int datagram_sock = -1; static short datagram_id = 1; static struct datagram_queue smb_datagram_queue; static mutex_t smb_dgq_mtx; static void smb_netbios_datagram_error(unsigned char *buf); /* * Function: smb_netbios_datagram_tick(void) * * Description: * * Called once a second to handle time to live timeouts in * datagram assembly queue. * * Inputs: * * Returns: * void -> Nothing at all... */ void smb_netbios_datagram_tick(void) { struct datagram *entry; struct datagram *next; (void) mutex_lock(&smb_dgq_mtx); for (entry = smb_datagram_queue.forw; entry != (struct datagram *)((uintptr_t)&smb_datagram_queue); entry = next) { next = entry->forw; if (--entry->discard_timer == 0) { /* Toss it */ QUEUE_CLIP(entry); free(entry); } } (void) mutex_unlock(&smb_dgq_mtx); } void smb_netbios_datagram_fini() { struct datagram *entry; (void) mutex_lock(&smb_dgq_mtx); while ((entry = smb_datagram_queue.forw) != (struct datagram *)((uintptr_t)&smb_datagram_queue)) { QUEUE_CLIP(entry); free(entry); } (void) mutex_unlock(&smb_dgq_mtx); } /* * Function: int smb_netbios_send_Bnode_datagram(unsigned char *data, * struct name_entry *source, struct name_entry *destination, * uint32_t broadcast) * * Description from rfc1002: * * 5.3.1. B NODE TRANSMISSION OF NetBIOS DATAGRAMS * * PROCEDURE send_datagram(data, source, destination, broadcast) * * (* * * user initiated processing on B node * *) * * BEGIN * group = FALSE; * * do name discovery on destination name, returns name type and * IP address; * * IF name type is group name THEN * BEGIN * group = TRUE; * END * * (* * * build datagram service UDP packet; * *) * convert source and destination NetBIOS names into * half-ASCII, biased encoded name; * SOURCE_NAME = cat(source, SCOPE_ID); * SOURCE_IP = this nodes IP address; * SOURCE_PORT = IPPORT_NETBIOS_DGM; * * IF NetBIOS broadcast THEN * BEGIN * DESTINATION_NAME = cat("*", SCOPE_ID) * END * ELSE * BEGIN * DESTINATION_NAME = cat(destination, SCOPE_ID) * END * * MSG_TYPE = select_one_from_set * {BROADCAST, DIRECT_UNIQUE, DIRECT_GROUP} * DGM_ID = next transaction id for Datagrams; * DGM_LENGTH = length of data + length of second level encoded * source and destination names; * * IF (length of the NetBIOS Datagram, including UDP and * IP headers, > MAX_DATAGRAM_LENGTH) THEN * BEGIN * (* * * fragment NetBIOS datagram into 2 UDP packets * *) * Put names into 1st UDP packet and any data that fits * after names; * Set MORE and FIRST bits in 1st UDP packets FLAGS; * OFFSET in 1st UDP = 0; * * Replicate NetBIOS Datagram header from 1st UDP packet * into 2nd UDP packet; * Put rest of data in 2nd UDP packet; * Clear MORE and FIRST bits in 2nd UDP packets FLAGS; * OFFSET in 2nd UDP = DGM_LENGTH - number of name and * data bytes in 1st UDP; * END * BEGIN * (* * * Only need one UDP packet * *) * USER_DATA = data; * Clear MORE bit and set FIRST bit in FLAGS; * OFFSET = 0; * END * * IF (group == TRUE) OR (NetBIOS broadcast) THEN * BEGIN * send UDP packet(s) to BROADCAST_ADDRESS; * END * ELSE * BEGIN * send UDP packet(s) to IP address returned by name * discovery; * END * END (* procedure *) * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | MSG_TYPE | FLAGS | DGM_ID | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | SOURCE_IP | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | SOURCE_PORT | DGM_LENGTH | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | PACKET_OFFSET | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * MSG_TYPE values (in hexidecimal): * * 10 - DIRECT_UNIQUE DATAGRAM * 11 - DIRECT_GROUP DATAGRAM * 12 - BROADCAST DATAGRAM * 13 - DATAGRAM ERROR * 14 - DATAGRAM QUERY REQUEST * 15 - DATAGRAM POSITIVE QUERY RESPONSE * 16 - DATAGRAM NEGATIVE QUERY RESPONSE * * Bit definitions of the FLAGS field: * * 0 1 2 3 4 5 6 7 * +---+---+---+---+---+---+---+---+ * | 0 | 0 | 0 | 0 | SNT | F | M | * +---+---+---+---+---+---+---+---+ * * Symbol Bit(s) Description * * M 7 MORE flag, If set then more NetBIOS datagram * fragments follow. * * F 6 FIRST packet flag, If set then this is first * (and possibly only) fragment of NetBIOS * datagram * * SNT 4,5 Source End-Node type: * 00 = B node * 01 = P node * 10 = M node * 11 = NBDD * RESERVED 0-3 Reserved, must be zero (0) * (But MS sets bit 3 in this field) * */ int smb_netbios_datagram_send(struct name_entry *src, struct name_entry *dest, unsigned char *data, int length) { smb_inaddr_t ipaddr; size_t count, srclen, destlen, sinlen; addr_entry_t *addr; struct sockaddr_in sin; char *buffer; char ha_source[NETBIOS_DOMAIN_NAME_MAX]; char ha_dest[NETBIOS_DOMAIN_NAME_MAX]; (void) smb_first_level_name_encode(src, (unsigned char *)ha_source, sizeof (ha_source)); srclen = strlen(ha_source) + 1; (void) smb_first_level_name_encode(dest, (unsigned char *)ha_dest, sizeof (ha_dest)); destlen = strlen(ha_dest) + 1; /* give some extra room */ if ((buffer = malloc(MAX_DATAGRAM_LENGTH * 4)) == NULL) { syslog(LOG_ERR, "nbt datagram: send: %m"); return (-1); } buffer[0] = DATAGRAM_TYPE_DIRECT_UNIQUE; switch (smb_node_type) { case 'B': buffer[1] = DATAGRAM_FLAGS_B_NODE | DATAGRAM_FLAGS_FIRST; break; case 'P': buffer[1] = DATAGRAM_FLAGS_P_NODE | DATAGRAM_FLAGS_FIRST; break; case 'M': buffer[1] = DATAGRAM_FLAGS_M_NODE | DATAGRAM_FLAGS_FIRST; break; case 'H': default: buffer[1] = DATAGRAM_FLAGS_H_NODE | DATAGRAM_FLAGS_FIRST; break; } datagram_id++; BE_OUT16(&buffer[2], datagram_id); (void) memcpy(&buffer[4], &src->addr_list.sin.sin_addr.s_addr, sizeof (uint32_t)); (void) memcpy(&buffer[8], &src->addr_list.sin.sin_port, sizeof (uint16_t)); BE_OUT16(&buffer[10], length + srclen + destlen); BE_OUT16(&buffer[12], 0); bcopy(ha_source, &buffer[14], srclen); bcopy(ha_dest, &buffer[14 + srclen], destlen); bcopy(data, &buffer[14 + srclen + destlen], length); count = &buffer[14 + srclen + destlen + length] - buffer; bzero(&sin, sizeof (sin)); sin.sin_family = AF_INET; sinlen = sizeof (sin); addr = &dest->addr_list; do { ipaddr.a_ipv4 = addr->sin.sin_addr.s_addr; ipaddr.a_family = AF_INET; /* Don't send anything to myself... */ if (smb_nic_is_local(&ipaddr)) goto next; sin.sin_addr.s_addr = ipaddr.a_ipv4; sin.sin_port = addr->sin.sin_port; (void) sendto(datagram_sock, buffer, count, 0, (struct sockaddr *)&sin, sinlen); next: addr = addr->forw; } while (addr != &dest->addr_list); free(buffer); return (0); } int smb_netbios_datagram_send_to_net(struct name_entry *src, struct name_entry *dest, char *data, int length) { smb_inaddr_t ipaddr; size_t count, srclen, destlen, sinlen; addr_entry_t *addr; struct sockaddr_in sin; char *buffer; char ha_source[NETBIOS_DOMAIN_NAME_MAX]; char ha_dest[NETBIOS_DOMAIN_NAME_MAX]; (void) smb_first_level_name_encode(src, (unsigned char *)ha_source, sizeof (ha_source)); srclen = strlen(ha_source) + 1; (void) smb_first_level_name_encode(dest, (unsigned char *)ha_dest, sizeof (ha_dest)); destlen = strlen(ha_dest) + 1; /* give some extra room */ if ((buffer = malloc(MAX_DATAGRAM_LENGTH * 4)) == NULL) { syslog(LOG_ERR, "nbt datagram: send_to_net: %m"); return (-1); } buffer[0] = DATAGRAM_TYPE_DIRECT_UNIQUE; switch (smb_node_type) { case 'B': buffer[1] = DATAGRAM_FLAGS_B_NODE | DATAGRAM_FLAGS_FIRST; break; case 'P': buffer[1] = DATAGRAM_FLAGS_P_NODE | DATAGRAM_FLAGS_FIRST; break; case 'M': buffer[1] = DATAGRAM_FLAGS_M_NODE | DATAGRAM_FLAGS_FIRST; break; case 'H': default: buffer[1] = DATAGRAM_FLAGS_H_NODE | DATAGRAM_FLAGS_FIRST; break; } datagram_id++; BE_OUT16(&buffer[2], datagram_id); (void) memcpy(&buffer[4], &src->addr_list.sin.sin_addr.s_addr, sizeof (uint32_t)); (void) memcpy(&buffer[8], &src->addr_list.sin.sin_port, sizeof (uint16_t)); BE_OUT16(&buffer[10], length + srclen + destlen); BE_OUT16(&buffer[12], 0); bcopy(ha_source, &buffer[14], srclen); bcopy(ha_dest, &buffer[14 + srclen], destlen); bcopy(data, &buffer[14 + srclen + destlen], length); count = &buffer[14 + srclen + destlen + length] - buffer; bzero(&sin, sizeof (sin)); sin.sin_family = AF_INET; sinlen = sizeof (sin); addr = &dest->addr_list; do { ipaddr.a_ipv4 = addr->sin.sin_addr.s_addr; ipaddr.a_family = AF_INET; if (smb_nic_is_local(&ipaddr)) goto next; sin.sin_addr.s_addr = ipaddr.a_ipv4; sin.sin_port = addr->sin.sin_port; (void) sendto(datagram_sock, buffer, count, 0, (struct sockaddr *)&sin, sinlen); next: addr = addr->forw; } while (addr != &dest->addr_list); free(buffer); return (0); } int smb_datagram_decode(struct datagram *datagram, int bytes) { unsigned char *ha_src; unsigned char *ha_dest; unsigned char *data; if (bytes == DATAGRAM_ERR_HEADER_LENGTH) { if (datagram->rawbuf[0] == DATAGRAM_TYPE_ERROR_DATAGRAM) smb_netbios_datagram_error(datagram->rawbuf); return (-1); } if (bytes >= DATAGRAM_HEADER_LENGTH) { ha_src = &datagram->rawbuf[DATAGRAM_HEADER_LENGTH]; ha_dest = ha_src + strlen((char *)ha_src) + 1; data = ha_dest + strlen((char *)ha_dest) + 1; bzero(&datagram->src, sizeof (struct name_entry)); bzero(&datagram->dest, sizeof (struct name_entry)); datagram->rawbytes = bytes; datagram->packet_type = datagram->rawbuf[0]; datagram->flags = datagram->rawbuf[1]; datagram->datagram_id = BE_IN16(&datagram->rawbuf[2]); datagram->src.addr_list.sinlen = sizeof (struct sockaddr_in); (void) memcpy(&datagram->src.addr_list.sin.sin_addr.s_addr, &datagram->rawbuf[4], sizeof (uint32_t)); (void) memcpy(&datagram->src.addr_list.sin.sin_port, &datagram->rawbuf[8], sizeof (uint16_t)); datagram->src.addr_list.forw = datagram->src.addr_list.back = &datagram->src.addr_list; datagram->data = data; datagram->data_length = BE_IN16(&datagram->rawbuf[10]); datagram->offset = BE_IN16(&datagram->rawbuf[12]); if (smb_first_level_name_decode(ha_src, &datagram->src) < 0) { smb_tracef("NbtDatagram[%s]: invalid calling name", inet_ntoa(datagram->src.addr_list.sin.sin_addr)); smb_tracef("Calling name: <%02X>%32.32s", ha_src[0], &ha_src[1]); } datagram->dest.addr_list.forw = datagram->dest.addr_list.back = &datagram->dest.addr_list; if (smb_first_level_name_decode(ha_dest, &datagram->dest) < 0) { smb_tracef("NbtDatagram[%s]: invalid called name", inet_ntoa(datagram->src.addr_list.sin.sin_addr)); smb_tracef("Called name: <%02X>%32.32s", ha_dest[0], &ha_dest[1]); } return (0); } /* ignore other malformed datagram packets */ return (-1); } /* * 4.4.3. Datagram Error Packet */ static void smb_netbios_datagram_error(unsigned char *buf) { int error; int datagram_id; if (buf[0] != DATAGRAM_TYPE_ERROR_DATAGRAM) return; datagram_id = BE_IN16(&buf[2]); error = buf[DATAGRAM_ERR_HEADER_LENGTH - 1]; switch (error) { case DATAGRAM_INVALID_SOURCE_NAME_FORMAT: smb_tracef("NbtDatagramError[%d]: invalid source name format", datagram_id); break; case DATAGRAM_INVALID_DESTINATION_NAME_FORMAT: smb_tracef("NbtDatagramError[%d]: invalid destination name " "format", datagram_id); break; case DATAGRAM_DESTINATION_NAME_NOT_PRESENT: default: break; } } /* * Function: int smb_netbios_process_BPM_datagram(unsigned char *packet, * addr_entry_t *addr) * * Description from rfc1002: * * 5.3.3. RECEPTION OF NetBIOS DATAGRAMS BY ALL NODES * * The following algorithm discards out of order NetBIOS Datagram * fragments. An implementation which reassembles out of order * NetBIOS Datagram fragments conforms to this specification. The * fragment discard timer is initialized to the value FRAGMENT_TIMEOUT. * This value should be user configurable. The default value is * given in Section 6, "Defined Constants and Variables". * * PROCEDURE datagram_packet(packet) * * (* * * processing initiated by datagram packet reception * * on B, P and M nodes * *) * BEGIN * (* * * if this node is a P node, ignore * * broadcast packets. * *) * * IF this is a P node AND incoming packet is * a broadcast packet THEN * BEGIN * discard packet; * END * * CASE packet type OF * * DATAGRAM SERVICE: * BEGIN * IF FIRST bit in FLAGS is set THEN * BEGIN * IF MORE bit in FLAGS is set THEN * BEGIN * Save 1st UDP packet of the Datagram; * Set this Datagrams fragment discard * timer to FRAGMENT_TIMEOUT; * return; * END * ELSE * Datagram is composed of a single * UDP packet; * END * ELSE * BEGIN * (* Have the second fragment of a Datagram *) * * Search for 1st fragment by source IP address * and DGM_ID; * IF found 1st fragment THEN * Process both UDP packets; * ELSE * BEGIN * discard 2nd fragment UDP packet; * return; * END * END * * IF DESTINATION_NAME is '*' THEN * BEGIN * (* NetBIOS broadcast *) * * deliver USER_DATA from UDP packet(s) to all * outstanding receive broadcast * datagram requests; * return; * END * ELSE * BEGIN (* non-broadcast *) * (* Datagram for Unique or Group Name *) * * IF DESTINATION_NAME is not present in the * local name table THEN * BEGIN * (* destination not present *) * build DATAGRAM ERROR packet, clear * FIRST and MORE bit, put in * this nodes IP and PORT, set * ERROR_CODE; * send DATAGRAM ERROR packet to * source IP address and port * of UDP; * discard UDP packet(s); * return; * END * ELSE * BEGIN (* good *) * (* * * Replicate received NetBIOS datagram for * * each recipient * *) * FOR EACH pending NetBIOS users receive * datagram operation * BEGIN * IF source name of operation * matches destination name * of packet THEN * BEGIN * deliver USER_DATA from UDP * packet(s); * END * END (* for each *) * return; * END (* good *) * END (* non-broadcast *) * END (* datagram service *) * * DATAGRAM ERROR: * BEGIN * (* * * name service returned incorrect information * *) * * inform local name service that incorrect * information was provided; * * IF this is a P or M node THEN * BEGIN * (* * * tell NetBIOS Name Server that it may * * have given incorrect information * *) * * send NAME RELEASE REQUEST with name * and incorrect IP address to NetBIOS * Name Server; * END * END (* datagram error *) * * END (* case *) * END */ static struct datagram * smb_netbios_datagram_getq(struct datagram *datagram) { struct datagram *prev = 0; (void) mutex_lock(&smb_dgq_mtx); for (prev = smb_datagram_queue.forw; prev != (struct datagram *)((uintptr_t)&smb_datagram_queue); prev = prev->forw) { if (prev->src.addr_list.sin.sin_addr.s_addr == datagram->src.addr_list.sin.sin_addr.s_addr) { /* Something waiting */ QUEUE_CLIP(prev); (void) mutex_unlock(&smb_dgq_mtx); bcopy(datagram->data, &prev->data[prev->data_length], datagram->data_length); prev->data_length += datagram->data_length; free(datagram); return (prev); } } (void) mutex_unlock(&smb_dgq_mtx); return (0); } static void smb_netbios_BPM_datagram(struct datagram *datagram) { struct name_entry *entry = 0; struct datagram *qpacket = 0; pthread_t browser_dispatch; switch (datagram->packet_type) { case DATAGRAM_TYPE_BROADCAST : if (smb_node_type == 'P') { /* * if this node is a P node, ignore * broadcast packets. */ break; } /* FALLTHROUGH */ case DATAGRAM_TYPE_DIRECT_UNIQUE : case DATAGRAM_TYPE_DIRECT_GROUP : if ((datagram->flags & DATAGRAM_FLAGS_FIRST) != 0) { if (datagram->flags & DATAGRAM_FLAGS_MORE) { /* Save 1st UDP packet of the Datagram */ datagram->discard_timer = FRAGMENT_TIMEOUT; (void) mutex_lock(&smb_dgq_mtx); QUEUE_INSERT_TAIL(&smb_datagram_queue, datagram) (void) mutex_unlock(&smb_dgq_mtx); return; } /* process datagram */ } else { qpacket = smb_netbios_datagram_getq(datagram); if (qpacket) { datagram = qpacket; goto process_datagram; } break; } process_datagram: entry = 0; if ((strcmp((char *)datagram->dest.name, "*") == 0) || ((entry = smb_netbios_cache_lookup(&datagram->dest)) != 0)) { if (entry) { int is_local = IS_LOCAL(entry->attributes); smb_netbios_cache_unlock_entry(entry); if (is_local) { (void) pthread_create(&browser_dispatch, 0, smb_browser_dispatch, (void *)datagram); (void) pthread_detach(browser_dispatch); return; } } datagram->rawbuf[0] = DATAGRAM_TYPE_ERROR_DATAGRAM; datagram->rawbuf[1] &= DATAGRAM_FLAGS_SRC_TYPE; (void) memcpy(&datagram->rawbuf[4], &datagram->src.addr_list.sin.sin_addr.s_addr, sizeof (uint32_t)); BE_OUT16(&datagram->rawbuf[8], IPPORT_NETBIOS_DGM); (void) sendto(datagram_sock, datagram->rawbuf, datagram->rawbytes, 0, (struct sockaddr *)&datagram->src.addr_list.sin, datagram->src.addr_list.sinlen); } break; case DATAGRAM_TYPE_ERROR_DATAGRAM : break; } free(datagram); } /* * NetBIOS Datagram Service (port 138) */ /*ARGSUSED*/ void * smb_netbios_datagram_service(void *arg) { struct sockaddr_in sin; struct datagram *datagram; int bytes, flag = 1; smb_inaddr_t ipaddr; (void) mutex_lock(&smb_dgq_mtx); bzero(&smb_datagram_queue, sizeof (smb_datagram_queue)); smb_datagram_queue.forw = smb_datagram_queue.back = (struct datagram *)((uintptr_t)&smb_datagram_queue); (void) mutex_unlock(&smb_dgq_mtx); if ((datagram_sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { syslog(LOG_ERR, "nbt datagram: socket failed: %m"); smb_netbios_event(NETBIOS_EVENT_ERROR); return (NULL); } flag = 1; (void) setsockopt(datagram_sock, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof (flag)); bzero(&sin, sizeof (sin)); sin.sin_family = AF_INET; sin.sin_port = htons(IPPORT_NETBIOS_DGM); if (bind(datagram_sock, (struct sockaddr *)&sin, sizeof (sin)) != 0) { syslog(LOG_ERR, "nbt datagram: bind(%d) failed: %m", IPPORT_NETBIOS_DGM); (void) close(datagram_sock); smb_netbios_event(NETBIOS_EVENT_ERROR); return (NULL); } flag = 1; (void) setsockopt(datagram_sock, SOL_SOCKET, SO_BROADCAST, &flag, sizeof (flag)); smb_netbios_event(NETBIOS_EVENT_DGM_START); while (smb_netbios_running()) { if ((datagram = malloc(sizeof (struct datagram))) == NULL) { /* Sleep for 10 seconds and try again */ smb_netbios_sleep(10); continue; } ignore: bzero(&datagram->inaddr, sizeof (addr_entry_t)); datagram->inaddr.sinlen = sizeof (datagram->inaddr.sin); datagram->inaddr.forw = datagram->inaddr.back = &datagram->inaddr; if ((bytes = recvfrom(datagram_sock, datagram->rawbuf, MAX_DATAGRAM_LENGTH, 0, (struct sockaddr *)&datagram->inaddr.sin, &datagram->inaddr.sinlen)) < 0) { syslog(LOG_ERR, "nbt datagram: recvfrom failed: %m"); smb_netbios_event(NETBIOS_EVENT_ERROR); break; } /* Ignore any incoming packets from myself... */ ipaddr.a_ipv4 = datagram->inaddr.sin.sin_addr.s_addr; ipaddr.a_family = AF_INET; if (smb_nic_is_local(&ipaddr)) { goto ignore; } if (smb_datagram_decode(datagram, bytes) < 0) goto ignore; /* * This code was doing the wrong thing with responses from a * Windows2000 PDC because both DATAGRAM_FLAGS_H_NODE and * DATAGRAM_FLAGS_NBDD are defined to be the same value (see * netbios.h). Since the Windows2000 PDC wants to be an H-Node, * we need to handle all messages via smb_netbios_BPM_datagram. * * if ((datagram->flags & DATAGRAM_FLAGS_SRC_TYPE) == * DATAGRAM_FLAGS_NBDD) * smb_netbios_NBDD_datagram(datagram); * else * smb_netbios_BPM_datagram(datagram); */ smb_netbios_BPM_datagram(datagram); } smb_netbios_event(NETBIOS_EVENT_DGM_STOP); (void) smb_netbios_wait(NETBIOS_EVENT_BROWSER_STOP); (void) close(datagram_sock); smb_netbios_datagram_fini(); return (NULL); } static char /* LINTED - E_STATIC_UNUSED */ nb_fmt_flags(unsigned char flags) { switch (flags & DATAGRAM_FLAGS_SRC_TYPE) { case DATAGRAM_FLAGS_B_NODE: return ('B'); case DATAGRAM_FLAGS_P_NODE: return ('P'); case DATAGRAM_FLAGS_M_NODE: return ('M'); case DATAGRAM_FLAGS_H_NODE: return ('H'); default: return ('?'); } } /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2016 by Delphix. All rights reserved. */ /* * NetBIOS name resolution node types. * * A B-node (broadcast node) uses broadcasts for name registration * and resolution. Routers typically do not forward broadcasts and * only computers on the local subnet will respond. * * A P-node (peer-to-peer node) uses a NetBIOS name server (WINS) * to resolve NetBIOS names, which allows it to work across routers. * In order to function in a P-node environment, all computers must * be configured to use the NetBIOS name server because P-nodes do * not broadcast on the network. * * A mixed node (M-node) behaves as a B-node by default. If it cannot * resolve the name via broadcast then it tries a NetBIOS name server * lookup (P-node). * * A hybrid node (H-node) behaves as a P-node by default. If it cannot * resolve the name using a NetBIOS name server then it resorts to * broadcasts (B-node). * * NetBIOS Name Service Protocols * * A REQUEST packet is always sent to the well known UDP port 137. * The destination address is normally either the IP broadcast address or * the address of the NAME - the address of the NAME server it set up at * initialization time. In rare cases, a request packet will be sent to * an end node, e.g. a NAME QUERY REQUEST sent to "challenge" a node. * * A RESPONSE packet is always sent to the source UDP port and source IP * address of the request packet. * * A DEMAND packet must always be sent to the well known UDP port 137. * There is no restriction on the target IP address. * * A transaction ID is a value composed from the requestor's IP address and * a unique 16 bit value generated by the originator of the transaction. */ #include #include #include #include #include #include #include #include #include #include #include #include /* * RFC 1002 4.2.1.1. HEADER */ #define QUESTION_TYPE_NETBIOS_GENERAL 0x20 #define QUESTION_TYPE_NETBIOS_STATUS 0x21 #define QUESTION_CLASS_INTERNET 0x0001 /* * RFC 1002 4.2.1.3. RESOURCE RECORD */ #define RR_TYPE_IP_ADDRESS_RESOURCE 0x0001 #define RR_TYPE_NAME_SERVER_RESOURCE 0x0002 #define RR_TYPE_NULL_RESOURCE 0x000A #define RR_TYPE_NETBIOS_RESOURCE 0x0020 #define RR_TYPE_NETBIOS_STATUS 0x0021 /* * * RESOURCE RECORD RR_CLASS field definitions */ #define RR_CLASS_INTERNET_CLASS 0x0001 /* * NB_FLAGS field of the RESOURCE RECORD RDATA field for RR_TYPE of NB. */ #define RR_FLAGS_NB_ONT_MASK 0x6000 #define RR_FLAGS_NB_ONT_B_NODE 0x0000 #define RR_FLAGS_NB_ONT_P_NODE 0x2000 #define RR_FLAGS_NB_ONT_M_NODE 0x4000 #define RR_FLAGS_NB_ONT_RESERVED 0x6000 #define RR_FLAGS_NB_GROUP_NAME 0x8000 #define NAME_FLAGS_PERMANENT_NAME 0x0200 #define NAME_FLAGS_ACTIVE_NAME 0x0400 #define NAME_FLAGS_CONFLICT 0x0800 #define NAME_FLAGS_DEREGISTER 0x1000 #define NAME_FLAGS_ONT_MASK 0x6000 #define NAME_FLAGS_ONT_B_NODE 0x0000 #define NAME_FLAGS_ONT_P_NODE 0x2000 #define NAME_FLAGS_ONT_M_NODE 0x4000 #define NAME_FLAGS_ONT_RESERVED 0x6000 #define NAME_FLAGS_GROUP_NAME 0x8000 #define MAX_NETBIOS_REPLY_DATA_SIZE 500 #define NAME_HEADER_SIZE 12 typedef struct nbt_name_reply { struct nbt_name_reply *forw; struct nbt_name_reply *back; struct name_packet *packet; addr_entry_t *addr; uint16_t name_trn_id; boolean_t reply_ready; } nbt_name_reply_t; char smb_node_type; static nbt_name_reply_t reply_queue; static mutex_t rq_mtx; static cond_t rq_cv; static mutex_t nbt_name_config_mtx; static name_queue_t delete_queue; static name_queue_t refresh_queue; static int name_sock = 0; static int bcast_num = 0; static int nbns_num = 0; static addr_entry_t smb_bcast_list[SMB_PI_MAX_NETWORKS]; static addr_entry_t smb_nbns[SMB_PI_MAX_WINS]; static int smb_netbios_process_response(uint16_t, addr_entry_t *, struct name_packet *, uint32_t); static int smb_send_name_service_packet(addr_entry_t *addr, struct name_packet *packet); /* * Allocate a transaction id. */ static uint16_t smb_netbios_name_trn_id(void) { static uint16_t trn_id; static mutex_t trn_id_mtx; (void) mutex_lock(&trn_id_mtx); do { ++trn_id; } while (trn_id == 0 || trn_id == (uint16_t)-1); (void) mutex_unlock(&trn_id_mtx); return (trn_id); } static int smb_end_node_challenge(nbt_name_reply_t *reply_info) { int rc; uint32_t retry; uint16_t tid; struct resource_record *answer; struct name_question question; addr_entry_t *addr; struct name_entry *destination; struct name_packet packet; struct timespec st; /* * The response packet has in it the address of the presumed owner * of the name. Challenge that owner. If owner either does not * respond or indicates that they no longer own the name, claim the * name. Otherwise, the name cannot be claimed. */ if ((answer = reply_info->packet->answer) == 0) return (-1); destination = answer->name; question.name = answer->name; packet.info = NAME_QUERY_REQUEST | NM_FLAGS_UNICAST; packet.qdcount = 1; /* question entries */ packet.question = &question; packet.ancount = 0; /* answer recs */ packet.answer = NULL; packet.nscount = 0; /* authority recs */ packet.authority = NULL; packet.arcount = 0; /* additional recs */ packet.additional = NULL; addr = &destination->addr_list; for (retry = 0; retry < UCAST_REQ_RETRY_COUNT; retry++) { tid = smb_netbios_name_trn_id(); packet.name_trn_id = tid; if (smb_send_name_service_packet(addr, &packet) >= 0) { if ((rc = smb_netbios_process_response(tid, addr, &packet, UCAST_REQ_RETRY_TIMEOUT)) != 0) return (rc); } st.tv_sec = 0; st.tv_nsec = (UCAST_REQ_RETRY_TIMEOUT * 1000000); (void) nanosleep(&st, 0); } /* No reply */ return (0); } static nbt_name_reply_t * smb_name_get_reply(uint16_t tid, uint32_t timeout) { uint16_t info; struct resource_record *answer; nbt_name_reply_t *reply; uint32_t wait_time, to_save; /* in millisecond */ struct timeval wt; timestruc_t to; to_save = timeout; reply = malloc(sizeof (nbt_name_reply_t)); if (reply != NULL) { reply->reply_ready = B_FALSE; reply->name_trn_id = tid; (void) mutex_lock(&rq_mtx); QUEUE_INSERT_TAIL(&reply_queue, reply); (void) mutex_unlock(&rq_mtx); for (;;) { (void) gettimeofday(&wt, 0); wait_time = wt.tv_usec / 1000; to.tv_sec = 0; to.tv_nsec = timeout * 1000000; (void) mutex_lock(&rq_mtx); (void) cond_reltimedwait(&rq_cv, &rq_mtx, &to); (void) mutex_unlock(&rq_mtx); if (reply->reply_ready) { info = reply->packet->info; if (PACKET_TYPE(info) == WACK_RESPONSE) { answer = reply->packet->answer; wait_time = (answer) ? TO_MILLISECONDS(answer->ttl) : DEFAULT_TTL; free(reply->addr); free(reply->packet); timeout = to_save + wait_time; reply->reply_ready = B_FALSE; reply->name_trn_id = tid; (void) mutex_lock(&rq_mtx); QUEUE_INSERT_TAIL(&reply_queue, reply); (void) mutex_unlock(&rq_mtx); continue; } return (reply); } (void) gettimeofday(&wt, 0); wait_time = (wt.tv_usec / 1000) - wait_time; if (wait_time >= timeout) { (void) mutex_lock(&rq_mtx); QUEUE_CLIP(reply); (void) mutex_unlock(&rq_mtx); free(reply); break; } timeout -= wait_time; } } return (0); } static void smb_reply_ready(struct name_packet *packet, addr_entry_t *addr) { nbt_name_reply_t *reply; struct resource_record *answer; (void) mutex_lock(&rq_mtx); for (reply = reply_queue.forw; reply != &reply_queue; reply = reply->forw) { if (reply->name_trn_id == packet->name_trn_id) { QUEUE_CLIP(reply); reply->addr = addr; reply->packet = packet; reply->reply_ready = B_TRUE; (void) cond_signal(&rq_cv); (void) mutex_unlock(&rq_mtx); return; } } (void) mutex_unlock(&rq_mtx); /* Presumably nobody is waiting any more... */ free(addr); answer = packet->answer; if (answer) smb_netbios_name_freeaddrs(answer->name); free(packet); } static int smb_netbios_process_response(uint16_t tid, addr_entry_t *addr, struct name_packet *packet, uint32_t timeout) { int rc = 0; uint16_t info; nbt_name_reply_t *reply; struct resource_record *answer; struct name_entry *name; struct name_entry *entry; struct name_question *question; uint32_t ttl; if ((reply = smb_name_get_reply(tid, timeout)) == 0) { return (0); /* No reply: retry */ } info = reply->packet->info; answer = reply->packet->answer; /* response */ switch (PACKET_TYPE(info)) { case NAME_QUERY_RESPONSE: if (POSITIVE_RESPONSE(info)) { addr = &answer->name->addr_list; do { /* * Make sure that remote name is not * flagged local */ addr->attributes &= ~NAME_ATTR_LOCAL; if (answer->ttl) addr->ttl = answer->ttl; else addr->ttl = DEFAULT_TTL; addr->refresh_ttl = TO_SECONDS(addr->ttl); addr->ttl = addr->refresh_ttl; addr = addr->forw; } while (addr != &answer->name->addr_list); smb_netbios_name_logf(answer->name); (void) smb_netbios_cache_insert_list(answer->name); rc = 1; } else { rc = -1; } break; case NAME_REGISTRATION_RESPONSE: if (NEGATIVE_RESPONSE(info)) { if (RCODE(info) == RCODE_CFT_ERR) { if (answer == 0) { rc = -RCODE(info); break; } name = answer->name; entry = smb_netbios_cache_lookup(name); if (entry) { /* * a name in the state "conflict * detected" does not "logically" exist * on that node. No further session * will be accepted on that name. * No datagrams can be sent against * that name. * Such an entry will not be used for * purposes of processing incoming * request packets. * The only valid user NetBIOS operation * against such a name is DELETE NAME. */ entry->attributes |= NAME_ATTR_CONFLICT; syslog(LOG_DEBUG, "nbns: name conflict: %15.15s", entry->name); smb_netbios_cache_unlock_entry(entry); } } rc = -RCODE(info); break; } /* * name can be added: * adjust refresh timeout value, * TTL, for this name */ question = packet->question; ttl = (answer && answer->ttl) ? answer->ttl : DEFAULT_TTL; ttl = TO_SECONDS(ttl); if ((entry = smb_netbios_cache_lookup(question->name)) != 0) { addr = &entry->addr_list; do { if ((addr->refresh_ttl == 0) || (ttl < addr->refresh_ttl)) addr->refresh_ttl = addr->ttl = ttl; addr = addr->forw; } while (addr != &entry->addr_list); smb_netbios_cache_unlock_entry(entry); } rc = 1; break; case NAME_RELEASE_RESPONSE: rc = 1; break; case END_NODE_CHALLENGE_REGISTRATION_REQUEST: /* * The response packet has in it the * address of the presumed owner of the * name. Challenge that owner. If * owner either does not respond or * indicates that they no longer own the * name, claim the name. Otherwise, * the name cannot be claimed. */ rc = smb_end_node_challenge(reply); break; default: rc = 0; break; } if (answer) smb_netbios_name_freeaddrs(answer->name); free(reply->addr); free(reply->packet); free(reply); return (rc); /* retry */ } /* * smb_name_buf_from_packet * * Description: * Convert a NetBIOS Name Server Packet Block (npb) * into the bits and bytes destined for the wire. * The "buf" is used as a heap. * * Inputs: * char * buf -> Buffer, from the wire * unsigned n_buf -> Length of 'buf' * name_packet *npb -> Packet block, decode into * unsigned n_npb -> Max bytes in 'npb' * * Returns: * >0 -> Encode successful, value is length of packet in "buf" * -1 -> Hard error, can not possibly encode * -2 -> Need more memory in buf -- it's too small */ static int smb_name_buf_from_packet(unsigned char *buf, int n_buf, struct name_packet *npb) { addr_entry_t *raddr; unsigned char *heap = buf; unsigned char *end_heap = heap + n_buf; unsigned char comp_name_buf[MAX_NAME_LENGTH]; unsigned int tmp; int i, step; if (n_buf < NAME_HEADER_SIZE) return (-1); /* no header, impossible */ BE_OUT16(heap, npb->name_trn_id); heap += 2; BE_OUT16(heap, npb->info); heap += 2; BE_OUT16(heap, npb->qdcount); heap += 2; BE_OUT16(heap, npb->ancount); heap += 2; BE_OUT16(heap, npb->nscount); heap += 2; BE_OUT16(heap, npb->arcount); heap += 2; for (i = 0; i < npb->qdcount; i++) { if ((heap + 34 + 4) > end_heap) return (-2); (void) smb_first_level_name_encode(npb->question[i].name, comp_name_buf, sizeof (comp_name_buf)); (void) strcpy((char *)heap, (char *)comp_name_buf); heap += strlen((char *)comp_name_buf) + 1; BE_OUT16(heap, npb->question[i].question_type); heap += 2; BE_OUT16(heap, npb->question[i].question_class); heap += 2; } for (step = 1; step <= 3; step++) { struct resource_record *nrr; int n; /* truly ugly, but saves code copying */ if (step == 1) { n = npb->ancount; nrr = npb->answer; } else if (step == 2) { n = npb->nscount; nrr = npb->authority; } else { /* step == 3 */ n = npb->arcount; nrr = npb->additional; } for (i = 0; i < n; i++) { if ((heap + 34 + 10) > end_heap) return (-2); (void) smb_first_level_name_encode(nrr->name, comp_name_buf, sizeof (comp_name_buf)); (void) strcpy((char *)heap, (char *)comp_name_buf); heap += strlen((char *)comp_name_buf) + 1; BE_OUT16(heap, nrr[i].rr_type); heap += 2; BE_OUT16(heap, nrr[i].rr_class); heap += 2; BE_OUT32(heap, nrr[i].ttl); heap += 4; BE_OUT16(heap, nrr[i].rdlength); heap += 2; if ((tmp = nrr[i].rdlength) > 0) { if ((heap + tmp) > end_heap) return (-2); if (nrr[i].rr_type == NAME_RR_TYPE_NB && nrr[i].rr_class == NAME_RR_CLASS_IN && tmp >= 6 && nrr[i].rdata == 0) { tmp = nrr[i].name->attributes & (NAME_ATTR_GROUP | NAME_ATTR_OWNER_NODE_TYPE); BE_OUT16(heap, tmp); heap += 2; raddr = &nrr[i].name->addr_list; (void) memcpy(heap, &raddr->sin.sin_addr.s_addr, sizeof (uint32_t)); heap += 4; } else { bcopy(nrr[i].rdata, heap, tmp); heap += tmp; } } } } return (heap - buf); } /* * strnchr * * Lookup for character 'c' in first 'n' chars of string 's'. * Returns pointer to the found char, otherwise returns 0. */ static char * strnchr(const char *s, char c, int n) { char *ps = (char *)s; char *es = (char *)s + n; while (ps < es && *ps) { if (*ps == c) return (ps); ++ps; } if (*ps == '\0' && c == '\0') return (ps); return (0); } static boolean_t is_multihome(char *name) { return (smb_nic_getnum(name) > 1); } /* * smb_netbios_getname * * Get the Netbios name part of the given record. * Does some boundary checks. * * Returns the name length on success, otherwise * returns 0. */ static int smb_netbios_getname(char *name, char *buf, char *buf_end) { char *name_end; int name_len; if (buf >= buf_end) { /* no room for a NB name */ return (0); } name_end = strnchr(buf, '\0', buf_end - buf + 1); if (name_end == 0) { /* not a valid NB name */ return (0); } name_len = name_end - buf + 1; (void) strlcpy(name, buf, name_len); return (name_len); } /* * smb_name_buf_to_packet * * Convert the bits and bytes that came from the wire into a NetBIOS * Name Server Packet Block (npb). The "block" is used as a heap. * * Returns a pointer to a name packet on success. Otherwise, returns * a NULL pointer. */ static struct name_packet * smb_name_buf_to_packet(char *buf, int n_buf) { struct name_packet *npb; unsigned char *heap; unsigned char *scan = (unsigned char *)buf; unsigned char *scan_end = scan + n_buf; char name_buf[MAX_NAME_LENGTH]; struct resource_record *nrr = 0; int rc, i, n, nn, ns; uint16_t name_trn_id, info; uint16_t qdcount, ancount, nscount, arcount; addr_entry_t *next; int name_len; if (n_buf < NAME_HEADER_SIZE) { /* truncated header */ syslog(LOG_DEBUG, "nbns: short packet (%d bytes)", n_buf); return (NULL); } name_trn_id = BE_IN16(scan); scan += 2; info = BE_IN16(scan); scan += 2; qdcount = BE_IN16(scan); scan += 2; ancount = BE_IN16(scan); scan += 2; nscount = BE_IN16(scan); scan += 2; arcount = BE_IN16(scan); scan += 2; ns = sizeof (struct name_entry); n = n_buf + sizeof (struct name_packet) + ((unsigned)qdcount * (sizeof (struct name_question) + ns)) + ((unsigned)ancount * (sizeof (struct resource_record) + ns)) + ((unsigned)nscount * (sizeof (struct resource_record) + ns)) + ((unsigned)arcount * (sizeof (struct resource_record) + ns)); if ((npb = malloc(n)) == NULL) return (NULL); bzero(npb, n); heap = npb->block_data; npb->name_trn_id = name_trn_id; npb->info = info; npb->qdcount = qdcount; npb->ancount = ancount; npb->nscount = nscount; npb->arcount = arcount; /* scan is in position for question entries */ /* * Measure the space needed for the tables */ if (qdcount > 0) { /* LINTED - E_BAD_PTR_CAST_ALIGN */ npb->question = (struct name_question *)heap; heap += qdcount * sizeof (struct name_question); for (i = 0; i < qdcount; i++) { /* LINTED - E_BAD_PTR_CAST_ALIGN */ npb->question[i].name = (struct name_entry *)heap; heap += sizeof (struct name_entry); } } /* LINTED - E_BAD_PTR_CAST_ALIGN */ nrr = (struct resource_record *)heap; if (ancount > 0) { /* LINTED - E_BAD_PTR_CAST_ALIGN */ npb->answer = (struct resource_record *)heap; heap += ancount * sizeof (struct resource_record); } if (nscount > 0) { /* LINTED - E_BAD_PTR_CAST_ALIGN */ npb->authority = (struct resource_record *)heap; heap += nscount * sizeof (struct resource_record); } if (arcount > 0) { /* LINTED - E_BAD_PTR_CAST_ALIGN */ npb->additional = (struct resource_record *)heap; heap += arcount * sizeof (struct resource_record); } /* * Populate each resource_record's .name field. * Done as a second pass so that all resource records * (answer, authority, additional) are consecutive via nrr[i]. */ for (i = 0; i < (ancount + nscount + arcount); i++) { /* LINTED - E_BAD_PTR_CAST_ALIGN */ nrr[i].name = (struct name_entry *)heap; heap += sizeof (struct name_entry); } for (i = 0; i < npb->qdcount; i++) { name_len = smb_netbios_getname(name_buf, (char *)scan, (char *)scan_end); if (name_len <= 0) { free(npb); return (NULL); } smb_init_name_struct(NETBIOS_EMPTY_NAME, 0, 0, 0, 0, 0, 0, npb->question[i].name); rc = smb_first_level_name_decode((unsigned char *)name_buf, npb->question[i].name); if (rc < 0) { /* Couldn't decode the question name */ free(npb); return (NULL); } scan += name_len; if (scan + 4 > scan_end) { /* no room for Question Type(2) and Class(2) fields */ free(npb); return (NULL); } npb->question[i].question_type = BE_IN16(scan); scan += 2; npb->question[i].question_class = BE_IN16(scan); scan += 2; } /* * Cheat. Remaining sections are of the same resource_record * format. Table space is consecutive. */ for (i = 0; i < (ancount + nscount + arcount); i++) { if (scan[0] == 0xc0) { /* Namebuf is reused... */ rc = 2; } else { name_len = smb_netbios_getname(name_buf, (char *)scan, (char *)scan_end); if (name_len <= 0) { free(npb); return (NULL); } rc = name_len; } scan += rc; if (scan + 10 > scan_end) { /* * no room for RR_TYPE (2), RR_CLASS (2), TTL (4) and * RDLENGTH (2) fields. */ free(npb); return (NULL); } smb_init_name_struct(NETBIOS_EMPTY_NAME, 0, 0, 0, 0, 0, 0, nrr[i].name); if ((rc = smb_first_level_name_decode((unsigned char *)name_buf, nrr[i].name)) < 0) { free(npb); return (NULL); } nrr[i].rr_type = BE_IN16(scan); scan += 2; nrr[i].rr_class = BE_IN16(scan); scan += 2; nrr[i].ttl = BE_IN32(scan); scan += 4; nrr[i].rdlength = BE_IN16(scan); scan += 2; if ((n = nrr[i].rdlength) > 0) { if ((scan + n) > scan_end) { /* no room for RDATA */ free(npb); return (NULL); } bcopy(scan, heap, n); nn = n; if (nrr[i].rr_type == 0x0020 && nrr[i].rr_class == 0x01 && n >= 6) { while (nn) { if (nn == 6) next = &nrr[i].name->addr_list; else { next = malloc( sizeof (addr_entry_t)); if (next == 0) { /* not enough memory */ free(npb); return (NULL); } QUEUE_INSERT_TAIL( &nrr[i].name->addr_list, next); } nrr[i].name->attributes = BE_IN16(scan); next->sin.sin_family = AF_INET; next->sinlen = sizeof (next->sin); (void) memcpy( &next->sin.sin_addr.s_addr, scan + 2, sizeof (uint32_t)); next->sin.sin_port = htons(IPPORT_NETBIOS_DGM); nn -= 6; scan += 6; } } else { nrr[i].rdata = heap; scan += n; } heap += n; } } return (npb); } /* * smb_send_name_service_packet * * Description: * * Send out a name service packet to proper destination. * * Inputs: * struct netbios_name *dest -> NETBIOS name of destination * struct name_packet *packet -> Packet to send * * Returns: * success -> >0 * failure -> <=0 */ static int smb_send_name_service_packet(addr_entry_t *addr, struct name_packet *packet) { unsigned char buf[MAX_DATAGRAM_LENGTH]; int len; if ((len = smb_name_buf_from_packet(buf, sizeof (buf), packet)) < 0) { errno = EINVAL; return (-1); } return (sendto(name_sock, buf, len, MSG_EOR, (struct sockaddr *)&addr->sin, addr->sinlen)); } /* * smb_netbios_send_rcv * * This function sends the given NetBIOS packet to the given * address and get back the response. If send operation is not * successful, it's repeated 'retries' times. * * Returns: * 0 Unsuccessful send operation; no reply * 1 Got reply */ static int smb_netbios_send_rcv(int bcast, addr_entry_t *destination, struct name_packet *packet, uint32_t retries, uint32_t timeout) { uint32_t retry; uint16_t tid; struct timespec st; int rc; for (retry = 0; retry < retries; retry++) { if ((destination->flags & ADDR_FLAG_VALID) == 0) return (0); tid = smb_netbios_name_trn_id(); packet->name_trn_id = tid; if (smb_send_name_service_packet(destination, packet) >= 0) { rc = smb_netbios_process_response(tid, destination, packet, timeout); if ((rc > 0) || (bcast == BROADCAST)) return (1); if (rc != 0) return (0); } st.tv_sec = 0; st.tv_nsec = (timeout * 1000000); (void) nanosleep(&st, 0); } return (0); } /* * RFC 1002 4.2.2. NAME REGISTRATION REQUEST */ static int smb_send_name_registration_request(int bcast, struct name_question *question, struct resource_record *additional) { int gotreply = 0; uint32_t retries; uint32_t timeout; addr_entry_t *destination; struct name_packet packet; unsigned char type; int i, addr_num, rc; type = question->name->name[15]; if ((type != NBT_WKSTA) && (type != NBT_SERVER)) { syslog(LOG_DEBUG, "nbns: name registration bad type (0x%02x)", type); smb_netbios_name_logf(question->name); question->name->attributes &= ~NAME_ATTR_LOCAL; return (-1); } if (bcast == BROADCAST) { if (bcast_num == 0) return (0); destination = smb_bcast_list; addr_num = bcast_num; retries = BCAST_REQ_RETRY_COUNT; timeout = BCAST_REQ_RETRY_TIMEOUT; packet.info = NAME_REGISTRATION_REQUEST | NM_FLAGS_BROADCAST; } else { if (nbns_num == 0) return (0); destination = smb_nbns; addr_num = nbns_num; retries = UCAST_REQ_RETRY_COUNT; timeout = UCAST_REQ_RETRY_TIMEOUT; packet.info = NAME_REGISTRATION_REQUEST | NM_FLAGS_UNICAST; } packet.qdcount = 1; /* question entries */ packet.question = question; packet.ancount = 0; /* answer recs */ packet.answer = NULL; packet.nscount = 0; /* authority recs */ packet.authority = NULL; packet.arcount = 1; /* additional recs */ packet.additional = additional; if (IS_UNIQUE(question->name->attributes) && (is_multihome((char *)(question->name->name)))) packet.info |= NAME_MULTIHOME_REGISTRATION_REQUEST; for (i = 0; i < addr_num; i++) { /* * Only register with the Primary WINS server, * unless we got no reply. */ if ((bcast == UNICAST) && gotreply) break; rc = smb_netbios_send_rcv(bcast, &destination[i], &packet, retries, timeout); if (rc == 1) gotreply = 1; } return (gotreply); } /* * RFC 1002 4.2.4. NAME REFRESH REQUEST */ /*ARGSUSED*/ static int smb_send_name_refresh_request(int bcast, struct name_question *question, struct resource_record *additional, int force) { int rc = 0; int gotreply = 0; uint32_t retries; uint32_t timeout; addr_entry_t *addr; addr_entry_t *destination; struct name_packet packet; unsigned char type; int i, addr_num, q_addrs = 0; type = question->name->name[15]; if ((type != NBT_WKSTA) && (type != NBT_SERVER)) { syslog(LOG_DEBUG, "nbns: name refresh bad type (0x%02x)", type); smb_netbios_name_logf(question->name); question->name->attributes &= ~NAME_ATTR_LOCAL; return (-1); } switch (bcast) { case BROADCAST : if (bcast_num == 0) return (-1); destination = smb_bcast_list; addr_num = bcast_num; retries = BCAST_REQ_RETRY_COUNT; timeout = BCAST_REQ_RETRY_TIMEOUT; packet.info = NAME_REFRESH_REQUEST | NM_FLAGS_BROADCAST; break; case UNICAST : if (nbns_num == 0) return (-1); destination = smb_nbns; addr_num = nbns_num; retries = UCAST_REQ_RETRY_COUNT; timeout = UCAST_REQ_RETRY_TIMEOUT; packet.info = NAME_REFRESH_REQUEST | NM_FLAGS_UNICAST; break; default: destination = &question->name->addr_list; /* * the value of addr_num is irrelvant here, because * the code is going to do special_process so it doesn't * need the addr_num. We set a value here just to avoid * compiler warning. */ addr_num = 0; retries = UCAST_REQ_RETRY_COUNT; timeout = UCAST_REQ_RETRY_TIMEOUT; packet.info = NAME_REFRESH_REQUEST | NM_FLAGS_UNICAST; q_addrs = 1; break; } if (IS_UNIQUE(question->name->attributes) && (is_multihome((char *)(question->name->name)))) packet.info |= NAME_MULTIHOME_REGISTRATION_REQUEST; packet.qdcount = 1; /* question entries */ packet.question = question; packet.ancount = 0; /* answer recs */ packet.answer = NULL; packet.nscount = 0; /* authority recs */ packet.authority = NULL; packet.arcount = 1; /* additional recs */ packet.additional = additional; if (q_addrs) goto special_process; for (i = 0; i < addr_num; i++) { rc = smb_netbios_send_rcv(bcast, &destination[i], &packet, retries, timeout); if (rc == 1) gotreply = 1; } return (gotreply); special_process: addr = destination; do { rc = smb_netbios_send_rcv(bcast, addr, &packet, retries, timeout); if (rc == 1) gotreply = 1; addr = addr->forw; } while (addr != destination); return (gotreply); } /* * RFC 1002 4.2.5. POSITIVE NAME REGISTRATION RESPONSE * RFC 1002 4.2.6. NEGATIVE NAME REGISTRATION RESPONSE */ static int smb_send_name_registration_response(addr_entry_t *addr, struct name_packet *original_packet, uint16_t rcode) { struct name_packet packet; struct resource_record answer; bzero(&packet, sizeof (struct name_packet)); bzero(&answer, sizeof (struct resource_record)); packet.name_trn_id = original_packet->name_trn_id; packet.info = NAME_REGISTRATION_RESPONSE | NAME_NM_FLAGS_RA | (rcode & NAME_RCODE_MASK); packet.qdcount = 0; /* question entries */ packet.question = NULL; packet.ancount = 1; /* answer recs */ packet.answer = &answer; packet.nscount = 0; /* authority recs */ packet.authority = NULL; packet.arcount = 0; /* additional recs */ packet.additional = NULL; answer.name = original_packet->question->name; answer.rr_type = NAME_QUESTION_TYPE_NB; answer.rr_class = NAME_QUESTION_CLASS_IN; answer.ttl = original_packet->additional->ttl; answer.rdlength = original_packet->additional->rdlength; answer.rdata = original_packet->additional->rdata; return (smb_send_name_service_packet(addr, &packet)); } /* * RFC 1002 4.2.9. NAME RELEASE REQUEST & DEMAND */ static int smb_send_name_release_request_and_demand(int bcast, struct name_question *question, struct resource_record *additional) { int gotreply = 0; int i, rc; int addr_num; uint32_t retries; uint32_t timeout; addr_entry_t *destination; struct name_packet packet; if (bcast == BROADCAST) { if (bcast_num == 0) return (-1); destination = smb_bcast_list; addr_num = bcast_num; retries = 1; /* BCAST_REQ_RETRY_COUNT */ timeout = 100; /* BCAST_REQ_RETRY_TIMEOUT */ packet.info = NAME_RELEASE_REQUEST | NM_FLAGS_BROADCAST; } else { if (nbns_num == 0) return (-1); destination = smb_nbns; addr_num = nbns_num; retries = 1; /* UCAST_REQ_RETRY_COUNT */ timeout = 100; /* UCAST_REQ_RETRY_TIMEOUT */ packet.info = NAME_RELEASE_REQUEST | NM_FLAGS_UNICAST; } packet.qdcount = 1; /* question entries */ packet.question = question; packet.ancount = 0; /* answer recs */ packet.answer = NULL; packet.nscount = 0; /* authority recs */ packet.authority = NULL; packet.arcount = 1; /* additional recs */ packet.additional = additional; for (i = 0; i < addr_num; i++) { rc = smb_netbios_send_rcv(bcast, &destination[i], &packet, retries, timeout); if (rc == 1) gotreply = 1; } return (gotreply); } /* * RFC 1002 4.2.10. POSITIVE NAME RELEASE RESPONSE * RFC 1002 4.2.11. NEGATIVE NAME RELEASE RESPONSE */ static int /* LINTED - E_STATIC_UNUSED */ smb_send_name_release_response(addr_entry_t *addr, struct name_packet *original_packet, uint16_t rcode) { struct name_packet packet; struct resource_record answer; bzero(&packet, sizeof (struct name_packet)); bzero(&answer, sizeof (struct resource_record)); packet.name_trn_id = original_packet->name_trn_id; packet.info = NAME_RELEASE_RESPONSE | (rcode & NAME_RCODE_MASK); packet.qdcount = 0; /* question entries */ packet.question = NULL; packet.ancount = 1; /* answer recs */ packet.answer = &answer; packet.nscount = 0; /* authority recs */ packet.authority = NULL; packet.arcount = 0; /* additional recs */ packet.additional = NULL; answer.name = original_packet->question->name; answer.rr_type = NAME_QUESTION_TYPE_NB; answer.rr_class = NAME_QUESTION_CLASS_IN; answer.ttl = original_packet->additional->ttl; answer.rdlength = original_packet->additional->rdlength; answer.rdata = original_packet->additional->rdata; return (smb_send_name_service_packet(addr, &packet)); } /* * RFC 1002 4.2.12. NAME QUERY REQUEST */ static int smb_send_name_query_request(int bcast, struct name_question *question) { int rc = 0; uint32_t retry, retries; uint32_t timeout; uint16_t tid; addr_entry_t *destination; struct name_packet packet; int i, addr_num; struct timespec st; if (bcast == BROADCAST) { if (bcast_num == 0) return (-1); destination = smb_bcast_list; addr_num = bcast_num; retries = BCAST_REQ_RETRY_COUNT; timeout = BCAST_REQ_RETRY_TIMEOUT; packet.info = NAME_QUERY_REQUEST | NM_FLAGS_BROADCAST; } else { if (nbns_num == 0) return (-1); destination = smb_nbns; addr_num = nbns_num; retries = UCAST_REQ_RETRY_COUNT; timeout = UCAST_REQ_RETRY_TIMEOUT; packet.info = NAME_QUERY_REQUEST | NM_FLAGS_UNICAST; } packet.qdcount = 1; /* question entries */ packet.question = question; packet.ancount = 0; /* answer recs */ packet.answer = NULL; packet.nscount = 0; /* authority recs */ packet.authority = NULL; packet.arcount = 0; /* additional recs */ packet.additional = NULL; for (i = 0; i < addr_num; i++) { for (retry = 0; retry < retries; retry++) { if ((destination[i].flags & ADDR_FLAG_VALID) == 0) break; tid = smb_netbios_name_trn_id(); packet.name_trn_id = tid; if (smb_send_name_service_packet(&destination[i], &packet) >= 0) { if ((rc = smb_netbios_process_response(tid, &destination[i], &packet, timeout)) != 0) break; } st.tv_sec = 0; st.tv_nsec = (timeout * 1000000); (void) nanosleep(&st, 0); } } return (rc); } /* * RFC 1002 4.2.13. POSITIVE NAME QUERY RESPONSE * RFC 1002 4.2.14. NEGATIVE NAME QUERY RESPONSE */ static int smb_send_name_query_response(addr_entry_t *addr, struct name_packet *original_packet, struct name_entry *entry, uint16_t rcode) { addr_entry_t *raddr; struct name_packet packet; struct resource_record answer; uint16_t attr; unsigned char data[MAX_DATAGRAM_LENGTH]; unsigned char *scan = data; uint32_t ret_addr; packet.name_trn_id = original_packet->name_trn_id; packet.info = NAME_QUERY_RESPONSE | (rcode & NAME_RCODE_MASK); packet.qdcount = 0; /* question entries */ packet.question = NULL; packet.ancount = 1; /* answer recs */ packet.answer = &answer; packet.nscount = 0; /* authority recs */ packet.authority = NULL; packet.arcount = 0; /* additional recs */ packet.additional = NULL; answer.name = entry; answer.rr_class = NAME_QUESTION_CLASS_IN; answer.ttl = entry->addr_list.ttl; answer.rdata = data; if (rcode) { answer.rr_type = NAME_RR_TYPE_NULL; answer.rdlength = 0; bzero(data, 6); } else { answer.rdlength = 0; answer.rr_type = NAME_QUESTION_TYPE_NB; raddr = &entry->addr_list; scan = data; do { attr = entry->attributes & (NAME_ATTR_GROUP | NAME_ATTR_OWNER_NODE_TYPE); BE_OUT16(scan, attr); scan += 2; ret_addr = LE_32(raddr->sin.sin_addr.s_addr); *scan++ = ret_addr; *scan++ = ret_addr >> 8; *scan++ = ret_addr >> 16; *scan++ = ret_addr >> 24; answer.rdlength += 6; raddr = raddr->forw; } while (raddr != &entry->addr_list); } return (smb_send_name_service_packet(addr, &packet)); } /* * RFC 1002 4.2.18. NODE STATUS RESPONSE */ static int smb_send_node_status_response(addr_entry_t *addr, struct name_packet *original_packet) { uint32_t net_ipaddr; int64_t max_connections; struct arpreq arpreq; struct name_packet packet; struct resource_record answer; unsigned char *scan; unsigned char *scan_end; unsigned char data[MAX_NETBIOS_REPLY_DATA_SIZE]; boolean_t scan_done = B_FALSE; smb_inaddr_t ipaddr; bzero(&packet, sizeof (struct name_packet)); bzero(&answer, sizeof (struct resource_record)); packet.name_trn_id = original_packet->name_trn_id; packet.info = NODE_STATUS_RESPONSE; packet.qdcount = 0; /* question entries */ packet.question = NULL; packet.ancount = 1; /* answer recs */ packet.answer = &answer; packet.nscount = 0; /* authority recs */ packet.authority = NULL; packet.arcount = 0; /* additional recs */ packet.additional = NULL; answer.name = original_packet->question->name; answer.rr_type = NAME_RR_TYPE_NBSTAT; answer.rr_class = NAME_QUESTION_CLASS_IN; answer.ttl = 0; answer.rdata = data; scan = smb_netbios_cache_status(data, MAX_NETBIOS_REPLY_DATA_SIZE, original_packet->question->name->scope); scan_end = data + MAX_NETBIOS_REPLY_DATA_SIZE; ipaddr.a_ipv4 = addr->sin.sin_addr.s_addr; ipaddr.a_family = AF_INET; if (smb_nic_is_same_subnet(&ipaddr)) net_ipaddr = addr->sin.sin_addr.s_addr; else net_ipaddr = 0; (void) smb_config_getnum(SMB_CI_MAX_CONNECTIONS, &max_connections); while (!scan_done) { if ((scan + 6) >= scan_end) { packet.info |= NAME_NM_FLAGS_TC; break; } if (net_ipaddr != 0) { struct sockaddr_in *s_in; int s; s = socket(AF_INET, SOCK_DGRAM, 0); /* LINTED - E_BAD_PTR_CAST_ALIGN */ s_in = (struct sockaddr_in *)&arpreq.arp_pa; s_in->sin_family = AF_INET; s_in->sin_addr.s_addr = net_ipaddr; if (ioctl(s, SIOCGARP, (caddr_t)&arpreq) < 0) { bzero(scan, 6); } else { bcopy(&arpreq.arp_ha.sa_data, scan, 6); } (void) close(s); } else { bzero(scan, 6); } scan += 6; if ((scan + 26) >= scan_end) { packet.info |= NAME_NM_FLAGS_TC; break; } bzero(scan, 26); scan += 26; if ((scan + 2) >= scan_end) { packet.info |= NAME_NM_FLAGS_TC; break; } BE_OUT16(scan, 0); scan += 2; if ((scan + 2) >= scan_end) { packet.info |= NAME_NM_FLAGS_TC; break; } BE_OUT16(scan, 0); scan += 2; if ((scan + 2) >= scan_end) { packet.info |= NAME_NM_FLAGS_TC; break; } BE_OUT16(scan, 0); scan += 2; if ((scan + 2) >= scan_end) { packet.info |= NAME_NM_FLAGS_TC; break; } BE_OUT16(scan, 0); scan += 2; if ((scan + 2) >= scan_end) { packet.info |= NAME_NM_FLAGS_TC; break; } BE_OUT16(scan, 0); scan += 2; if ((scan + 2) >= scan_end) { packet.info |= NAME_NM_FLAGS_TC; break; } BE_OUT16(scan, 0); scan += 2; if ((scan + 2) >= scan_end) { packet.info |= NAME_NM_FLAGS_TC; break; } BE_OUT16(scan, 0); scan += 2; if ((scan + 2) >= scan_end) { packet.info |= NAME_NM_FLAGS_TC; break; } BE_OUT16(scan, max_connections); scan += 2; if ((scan + 2) >= scan_end) { packet.info |= NAME_NM_FLAGS_TC; break; } BE_OUT16(scan, 0); scan += 2; scan_done = B_TRUE; } answer.rdlength = scan - data; return (smb_send_name_service_packet(addr, &packet)); } static int smb_name_Bnode_add_name(struct name_entry *name) { struct name_question question; struct resource_record additional; unsigned char data[8]; uint16_t attr; addr_entry_t *addr; int rc = 0; addr = &name->addr_list; do { /* build name service packet */ question.name = name; /* * question.name->attributes |= NAME_NB_FLAGS_ONT_B; * This is commented because NAME_NB_FLAGS_ONT_B is 0 */ question.question_type = NAME_QUESTION_TYPE_NB; question.question_class = NAME_QUESTION_CLASS_IN; additional.name = name; additional.rr_class = NAME_QUESTION_CLASS_IN; additional.ttl = 0; additional.rdata = data; additional.rdlength = 6; additional.rr_type = NAME_QUESTION_TYPE_NB; attr = name->attributes & (NAME_ATTR_GROUP | NAME_ATTR_OWNER_NODE_TYPE); BE_OUT16(&data[0], attr); (void) memcpy(&data[2], &addr->sin.sin_addr.s_addr, sizeof (uint32_t)); rc |= smb_send_name_registration_request(BROADCAST, &question, &additional); addr = addr->forw; } while (addr != &name->addr_list); return (rc); } static int smb_name_Bnode_find_name(struct name_entry *name) { struct name_question question; question.name = name; question.question_type = NAME_QUESTION_TYPE_NB; question.question_class = NAME_QUESTION_CLASS_IN; return (smb_send_name_query_request(BROADCAST, &question)); } static int smb_name_Bnode_delete_name(struct name_entry *name) { struct name_question question; struct resource_record additional; addr_entry_t *raddr; unsigned char data[MAX_DATAGRAM_LENGTH]; unsigned char *scan = data; uint32_t attr; uint32_t ret_addr; /* build packet */ question.name = name; question.question_type = NAME_QUESTION_TYPE_NB; question.question_class = NAME_QUESTION_CLASS_IN; additional.name = name; additional.rr_class = NAME_QUESTION_CLASS_IN; additional.ttl = 0; additional.rdata = data; additional.rdlength = 0; additional.rr_type = NAME_QUESTION_TYPE_NB; raddr = &name->addr_list; scan = data; do { attr = name->attributes & (NAME_ATTR_GROUP | NAME_ATTR_OWNER_NODE_TYPE); BE_OUT16(scan, attr); scan += 2; ret_addr = LE_32(raddr->sin.sin_addr.s_addr); *scan++ = ret_addr; *scan++ = ret_addr >> 8; *scan++ = ret_addr >> 16; *scan++ = ret_addr >> 24; additional.rdlength += 6; } while (raddr != &name->addr_list); return (smb_send_name_release_request_and_demand(BROADCAST, &question, &additional)); } static int smb_name_Pnode_add_name(struct name_entry *name) { struct name_question question; struct resource_record additional; unsigned char data[8]; uint16_t attr; addr_entry_t *addr; int rc = 0; /* build packet */ addr = &name->addr_list; do { question.name = name; question.question_type = NAME_QUESTION_TYPE_NB; question.question_class = NAME_QUESTION_CLASS_IN; additional.name = name; additional.rr_class = NAME_QUESTION_CLASS_IN; additional.ttl = 0; additional.rdata = data; additional.rdlength = 6; additional.rr_type = NAME_QUESTION_TYPE_NB; attr = name->attributes & (NAME_ATTR_GROUP | NAME_ATTR_OWNER_NODE_TYPE); BE_OUT16(&data[0], attr); (void) memcpy(&data[2], &addr->sin.sin_addr.s_addr, sizeof (uint32_t)); rc |= smb_send_name_registration_request(UNICAST, &question, &additional); addr = addr->forw; } while (addr != &name->addr_list); return (rc); } static int smb_name_Pnode_refresh_name(struct name_entry *name) { struct name_question question; struct resource_record additional; unsigned char data[8]; uint16_t attr; addr_entry_t *addr; int rc = 0; /* build packet */ addr = &name->addr_list; do { question.name = name; question.question_type = NAME_QUESTION_TYPE_NB; question.question_class = NAME_QUESTION_CLASS_IN; additional.name = name; additional.rr_class = NAME_QUESTION_CLASS_IN; additional.ttl = 0; additional.rdata = data; additional.rdlength = 6; additional.rr_type = NAME_QUESTION_TYPE_NB; attr = name->attributes & (NAME_ATTR_GROUP | NAME_ATTR_OWNER_NODE_TYPE); BE_OUT16(&data[0], attr); (void) memcpy(&data[2], &addr->sin.sin_addr.s_addr, sizeof (uint32_t)); rc |= smb_send_name_refresh_request(UNICAST, &question, &additional, 1); addr = addr->forw; } while (addr != &name->addr_list); return (rc); } static int smb_name_Pnode_find_name(struct name_entry *name) { struct name_question question; /* * Host initiated processing for a P node */ question.name = name; question.name->attributes |= NAME_NB_FLAGS_ONT_P; question.question_type = NAME_QUESTION_TYPE_NB; question.question_class = NAME_QUESTION_CLASS_IN; return (smb_send_name_query_request(UNICAST, &question)); } static int smb_name_Pnode_delete_name(struct name_entry *name) { struct name_question question; struct resource_record additional; addr_entry_t *raddr; unsigned char data[MAX_DATAGRAM_LENGTH]; unsigned char *scan = data; uint32_t attr; uint32_t ret_addr; /* build packet */ question.name = name; question.name->attributes |= NAME_NB_FLAGS_ONT_P; question.question_type = NAME_QUESTION_TYPE_NB; question.question_class = NAME_QUESTION_CLASS_IN; additional.name = name; additional.rr_class = NAME_QUESTION_CLASS_IN; additional.ttl = 0; additional.rdata = data; additional.rdlength = 0; additional.rr_type = NAME_QUESTION_TYPE_NB; raddr = &name->addr_list; do { scan = data; attr = name->attributes & (NAME_ATTR_GROUP | NAME_ATTR_OWNER_NODE_TYPE); BE_OUT16(scan, attr); scan += 2; ret_addr = LE_32(raddr->sin.sin_addr.s_addr); *scan++ = ret_addr; *scan++ = ret_addr >> 8; *scan++ = ret_addr >> 16; *scan++ = ret_addr >> 24; additional.rdlength = 6; raddr = raddr->forw; (void) smb_send_name_release_request_and_demand(UNICAST, &question, &additional); } while (raddr != &name->addr_list); return (1); } static int smb_name_Mnode_add_name(struct name_entry *name) { if (smb_name_Bnode_add_name(name) > 0) { if (nbns_num == 0) return (1); /* No name server configured */ return (smb_name_Pnode_add_name(name)); } return (-1); } static int smb_name_Hnode_add_name(struct name_entry *name) { if (nbns_num > 0) { if (smb_name_Pnode_add_name(name) == 1) return (1); } return (smb_name_Bnode_add_name(name)); } static int smb_name_Mnode_find_name(struct name_entry *name) { if (smb_name_Bnode_find_name(name) == 1) return (1); if (nbns_num == 0) return (1); /* No name server configured */ return (smb_name_Pnode_find_name(name)); } static int smb_name_Hnode_find_name(struct name_entry *name) { if (nbns_num > 0) if (smb_name_Pnode_find_name(name) == 1) return (1); return (smb_name_Bnode_find_name(name)); } static int smb_name_Mnode_delete_name(struct name_entry *name) { (void) smb_name_Bnode_delete_name(name); if (nbns_num == 0) return (-1); /* No name server configured */ if (smb_name_Pnode_delete_name(name) > 0) return (1); return (-1); } static int smb_name_Hnode_delete_name(struct name_entry *name) { if (nbns_num > 0) if (smb_name_Pnode_delete_name(name) > 0) return (1); return (smb_name_Bnode_delete_name(name)); } static void smb_name_process_Bnode_packet(struct name_packet *packet, addr_entry_t *addr) { struct name_entry *name; struct name_entry *entry; struct name_question *question; struct resource_record *additional; question = packet->question; additional = packet->additional; switch (packet->info & NAME_OPCODE_OPCODE_MASK) { case NAME_OPCODE_REFRESH: /* Guard against malformed packets */ if ((question == 0) || (additional == 0)) break; if (additional->name->addr_list.sin.sin_addr.s_addr == 0) break; name = question->name; name->addr_list.ttl = additional->ttl; name->attributes = additional->name->attributes; name->addr_list.sin = additional->name->addr_list.sin; name->addr_list.forw = name->addr_list.back = &name->addr_list; if ((entry = smb_netbios_cache_lookup_addr(name)) != 0) { smb_netbios_cache_update_entry(entry, question->name); smb_netbios_cache_unlock_entry(entry); } else (void) smb_netbios_cache_insert(question->name); break; case NAME_OPCODE_QUERY: /* * This opcode covers both NAME_QUERY_REQUEST and * NODE_STATUS_REQUEST. They can be distinguished * based on the type of question entry. */ /* All query requests have to have question entry */ if (question == 0) break; if (question->question_type == NAME_QUESTION_TYPE_NB) { name = question->name; if ((entry = smb_netbios_cache_lookup(name)) != 0) { (void) smb_send_name_query_response(addr, packet, entry, 0); smb_netbios_cache_unlock_entry(entry); } } else if (question->question_type == NAME_QUESTION_TYPE_NBSTAT) { /* * Name of "*" may be used to force node to * divulge status for administrative purposes */ name = question->name; entry = 0; if (NETBIOS_NAME_IS_STAR(name->name) || ((entry = smb_netbios_cache_lookup(name)) != 0)) { if (entry) smb_netbios_cache_unlock_entry(entry); /* * send only those names that are * in the same scope as the scope * field in the request packet */ (void) smb_send_node_status_response(addr, packet); } } break; default: break; } } static void smb_name_process_Pnode_packet(struct name_packet *packet, addr_entry_t *addr) { struct name_entry *name; struct name_entry *entry; struct name_question *question; struct resource_record *additional; question = packet->question; additional = packet->additional; if (packet->info & NAME_NM_FLAGS_B) { /* * always ignore UDP broadcast packets */ return; } switch (packet->info & NAME_OPCODE_OPCODE_MASK) { case NAME_OPCODE_REFRESH: /* Guard against malformed packets */ if ((question == 0) || (additional == 0)) break; if (additional->name->addr_list.sin.sin_addr.s_addr == 0) break; name = question->name; name->addr_list.ttl = additional->ttl; name->attributes = additional->name->attributes; name->addr_list.sin = additional->name->addr_list.sin; name->addr_list.forw = name->addr_list.back = &name->addr_list; if ((entry = smb_netbios_cache_lookup(name)) != 0) { smb_netbios_cache_update_entry(entry, name); smb_netbios_cache_unlock_entry(entry); } else (void) smb_netbios_cache_insert(name); (void) smb_send_name_registration_response(addr, packet, 0); break; case NAME_OPCODE_QUERY: /* * This opcode covers both NAME_QUERY_REQUEST and * NODE_STATUS_REQUEST. They can be distinguished * based on the type of question entry. */ /* All query requests have to have question entry */ if (question == 0) break; if (question->question_type == NAME_QUESTION_TYPE_NB) { name = question->name; if ((entry = smb_netbios_cache_lookup(name)) != 0) { /* * send response to the IP address and port * number from which the request was received. */ (void) smb_send_name_query_response(addr, packet, entry, 0); smb_netbios_cache_unlock_entry(entry); } else { /* * send response to the requestor */ (void) smb_send_name_query_response(addr, packet, name, RCODE_NAM_ERR); } } else if (question->question_type == NAME_QUESTION_TYPE_NBSTAT) { /* * Name of "*" may be used to force node to * divulge status for administrative purposes */ name = question->name; entry = 0; if (NETBIOS_NAME_IS_STAR(name->name) || ((entry = smb_netbios_cache_lookup(name)) != 0)) { /* * send only those names that are * in the same scope as the scope * field in the request packet */ if (entry) smb_netbios_cache_unlock_entry(entry); (void) smb_send_node_status_response(addr, packet); } } break; default: break; } } static void smb_name_process_Mnode_packet(struct name_packet *packet, addr_entry_t *addr) { if (packet->info & NAME_NM_FLAGS_B) smb_name_process_Bnode_packet(packet, addr); else smb_name_process_Pnode_packet(packet, addr); } static void smb_name_process_Hnode_packet(struct name_packet *packet, addr_entry_t *addr) { if (packet->info & NAME_NM_FLAGS_B) smb_name_process_Bnode_packet(packet, addr); else smb_name_process_Pnode_packet(packet, addr); } /* * smb_netbios_name_tick * * Called once a second to handle name server timeouts. */ void smb_netbios_name_tick(void) { struct name_entry *name; struct name_entry *entry; (void) mutex_lock(&refresh_queue.mtx); smb_netbios_cache_refresh(&refresh_queue); while ((name = refresh_queue.head.forw) != &refresh_queue.head) { QUEUE_CLIP(name); if (IS_LOCAL(name->attributes)) { if (IS_UNIQUE(name->attributes)) { (void) smb_name_Pnode_refresh_name(name); } } else { entry = smb_name_find_name(name); smb_name_unlock_name(entry); } free(name); } (void) mutex_unlock(&refresh_queue.mtx); smb_netbios_cache_reset_ttl(); } /* * smb_name_find_name * * Lookup name cache for the given name. * If it's not in the cache it'll send a * name query request and then lookup the * cache again. Note that if a name is * returned it's locked and called MUST * unlock it by calling smb_name_unlock_name() */ struct name_entry * smb_name_find_name(struct name_entry *name) { struct name_entry *result; if ((result = smb_netbios_cache_lookup(name)) == 0) { switch (smb_node_type) { case 'B': (void) smb_name_Bnode_find_name(name); break; case 'P': (void) smb_name_Pnode_find_name(name); break; case 'M': (void) smb_name_Mnode_find_name(name); break; case 'H': default: (void) smb_name_Hnode_find_name(name); break; } return (smb_netbios_cache_lookup(name)); } return (result); } void smb_name_unlock_name(struct name_entry *name) { smb_netbios_cache_unlock_entry(name); } int smb_name_add_name(struct name_entry *name) { int rc = 1; smb_netbios_name_logf(name); switch (smb_node_type) { case 'B': rc = smb_name_Bnode_add_name(name); break; case 'P': rc = smb_name_Pnode_add_name(name); break; case 'M': rc = smb_name_Mnode_add_name(name); break; case 'H': default: rc = smb_name_Hnode_add_name(name); break; } if (rc >= 0) (void) smb_netbios_cache_insert(name); return (rc); } int smb_name_delete_name(struct name_entry *name) { int rc; unsigned char type; type = name->name[15]; if ((type != NBT_WKSTA) && (type != NBT_SERVER)) { syslog(LOG_DEBUG, "nbns: name delete bad type (0x%02x)", type); smb_netbios_name_logf(name); name->attributes &= ~NAME_ATTR_LOCAL; return (-1); } smb_netbios_cache_delete(name); switch (smb_node_type) { case 'B': rc = smb_name_Bnode_delete_name(name); break; case 'P': rc = smb_name_Pnode_delete_name(name); break; case 'M': rc = smb_name_Mnode_delete_name(name); break; case 'H': default: rc = smb_name_Hnode_delete_name(name); break; } if (rc > 0) return (0); return (-1); } typedef struct { addr_entry_t *addr; char *buf; int length; } worker_param_t; /* * smb_netbios_worker * * Process incoming request/response packets for Netbios * name service (on port 138). */ void * smb_netbios_worker(void *arg) { worker_param_t *p = (worker_param_t *)arg; addr_entry_t *addr = p->addr; struct name_packet *packet; if ((packet = smb_name_buf_to_packet(p->buf, p->length)) != NULL) { if (packet->info & NAME_OPCODE_R) { /* Reply packet */ smb_reply_ready(packet, addr); free(p->buf); free(p); return (NULL); } /* Request packet */ switch (smb_node_type) { case 'B': smb_name_process_Bnode_packet(packet, addr); break; case 'P': smb_name_process_Pnode_packet(packet, addr); break; case 'M': smb_name_process_Mnode_packet(packet, addr); break; case 'H': default: smb_name_process_Hnode_packet(packet, addr); break; } if (packet->answer) smb_netbios_name_freeaddrs(packet->answer->name); free(packet); } else { syslog(LOG_ERR, "nbns: packet decode failed"); } free(addr); free(p->buf); free(p); return (NULL); } /* * Configure the node type. If a WINS server has been specified, * act like an H-node. Otherwise, behave like a B-node. */ static void smb_netbios_node_config(void) { static smb_cfg_id_t wins[SMB_PI_MAX_WINS] = { SMB_CI_WINS_SRV1, SMB_CI_WINS_SRV2 }; char ipstr[16]; uint32_t ipaddr; int i; smb_node_type = SMB_NODETYPE_B; nbns_num = 0; bzero(smb_nbns, sizeof (addr_entry_t) * SMB_PI_MAX_WINS); for (i = 0; i < SMB_PI_MAX_WINS; ++i) { ipstr[0] = '\0'; (void) smb_config_getstr(wins[i], ipstr, sizeof (ipstr)); if ((ipaddr = inet_addr(ipstr)) == INADDR_NONE) continue; smb_node_type = SMB_NODETYPE_H; smb_nbns[nbns_num].flags = ADDR_FLAG_VALID; smb_nbns[nbns_num].sinlen = sizeof (struct sockaddr_in); smb_nbns[nbns_num].sin.sin_family = AF_INET; smb_nbns[nbns_num].sin.sin_addr.s_addr = ipaddr; smb_nbns[nbns_num].sin.sin_port = htons(IPPORT_NETBIOS_NS); nbns_num++; } } static void smb_netbios_name_registration(void) { nbcache_iter_t nbc_iter; struct name_entry *name; int rc; rc = smb_netbios_cache_getfirst(&nbc_iter); while (rc == 0) { name = nbc_iter.nbc_entry; (void) smb_netbios_name_logf(name); if (IS_UNIQUE(name->attributes) && IS_LOCAL(name->attributes)) { switch (smb_node_type) { case SMB_NODETYPE_B: (void) smb_name_Bnode_add_name(name); break; case SMB_NODETYPE_P: (void) smb_name_Pnode_add_name(name); break; case SMB_NODETYPE_M: (void) smb_name_Mnode_add_name(name); break; case SMB_NODETYPE_H: default: (void) smb_name_Hnode_add_name(name); break; } } free(name); rc = smb_netbios_cache_getnext(&nbc_iter); } } /* * Note that the node configuration must be setup before calling * smb_init_name_struct(). */ void smb_netbios_name_config(void) { addr_entry_t *bcast_entry; struct name_entry name; smb_niciter_t ni; int rc; (void) mutex_lock(&nbt_name_config_mtx); smb_netbios_node_config(); bcast_num = 0; bzero(smb_bcast_list, sizeof (addr_entry_t) * SMB_PI_MAX_NETWORKS); rc = smb_nic_getfirst(&ni); while (rc == SMB_NIC_SUCCESS) { if ((ni.ni_nic.nic_smbflags & SMB_NICF_NBEXCL) || (ni.ni_nic.nic_smbflags & SMB_NICF_ALIAS)) { rc = smb_nic_getnext(&ni); continue; } bcast_entry = &smb_bcast_list[bcast_num]; bcast_entry->flags = ADDR_FLAG_VALID; bcast_entry->attributes = NAME_ATTR_LOCAL; bcast_entry->sinlen = sizeof (struct sockaddr_in); bcast_entry->sin.sin_family = AF_INET; bcast_entry->sin.sin_port = htons(IPPORT_NETBIOS_NS); bcast_entry->sin.sin_addr.s_addr = ni.ni_nic.nic_bcast; bcast_num++; smb_init_name_struct((unsigned char *)ni.ni_nic.nic_host, NBT_WKSTA, 0, ni.ni_nic.nic_ip.a_ipv4, htons(IPPORT_NETBIOS_DGM), NAME_ATTR_UNIQUE, NAME_ATTR_LOCAL, &name); (void) smb_netbios_cache_insert(&name); smb_init_name_struct((unsigned char *)ni.ni_nic.nic_host, NBT_SERVER, 0, ni.ni_nic.nic_ip.a_ipv4, htons(IPPORT_NETBIOS_DGM), NAME_ATTR_UNIQUE, NAME_ATTR_LOCAL, &name); (void) smb_netbios_cache_insert(&name); rc = smb_nic_getnext(&ni); } smb_netbios_name_registration(); (void) mutex_unlock(&nbt_name_config_mtx); } void smb_netbios_name_unconfig(void) { struct name_entry *name; (void) mutex_lock(&nbt_name_config_mtx); (void) mutex_lock(&delete_queue.mtx); smb_netbios_cache_delete_locals(&delete_queue); while ((name = delete_queue.head.forw) != &delete_queue.head) { QUEUE_CLIP(name); (void) smb_name_delete_name(name); free(name); } (void) mutex_unlock(&delete_queue.mtx); (void) mutex_unlock(&nbt_name_config_mtx); } void smb_netbios_name_reconfig(void) { smb_netbios_name_unconfig(); smb_netbios_name_config(); } /* * NetBIOS Name Service (port 137) */ /*ARGSUSED*/ void * smb_netbios_name_service(void *arg) { struct sockaddr_in sin; addr_entry_t *addr; int len; int flag = 1; char *buf; worker_param_t *worker_param; smb_inaddr_t ipaddr; /* * Initialize reply_queue */ bzero(&reply_queue, sizeof (reply_queue)); reply_queue.forw = reply_queue.back = &reply_queue; if ((name_sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { syslog(LOG_ERR, "nbns: socket failed: %m"); smb_netbios_event(NETBIOS_EVENT_ERROR); return (NULL); } flag = 1; (void) setsockopt(name_sock, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof (flag)); flag = 1; (void) setsockopt(name_sock, SOL_SOCKET, SO_BROADCAST, &flag, sizeof (flag)); bzero(&sin, sizeof (struct sockaddr_in)); sin.sin_family = AF_INET; sin.sin_port = htons(IPPORT_NETBIOS_NS); if (bind(name_sock, (struct sockaddr *)&sin, sizeof (sin)) != 0) { syslog(LOG_ERR, "nbns: bind(%d) failed: %m", IPPORT_NETBIOS_NS); (void) close(name_sock); smb_netbios_event(NETBIOS_EVENT_ERROR); return (NULL); } smb_netbios_event(NETBIOS_EVENT_NS_START); while (smb_netbios_running()) { buf = malloc(MAX_DATAGRAM_LENGTH); addr = malloc(sizeof (addr_entry_t)); if ((buf == NULL) || (addr == NULL)) { /* Sleep for 10 seconds and try again */ free(addr); free(buf); smb_netbios_sleep(10); continue; } ignore: bzero(addr, sizeof (addr_entry_t)); addr->sinlen = sizeof (addr->sin); addr->forw = addr->back = addr; if ((len = recvfrom(name_sock, buf, MAX_DATAGRAM_LENGTH, 0, (struct sockaddr *)&addr->sin, &addr->sinlen)) < 0) { if (errno == ENOMEM || errno == ENFILE || errno == EMFILE) { /* Sleep for 10 seconds and try again */ free(buf); free(addr); smb_netbios_sleep(10); continue; } syslog(LOG_ERR, "nbns: recvfrom failed: %m"); free(buf); free(addr); smb_netbios_event(NETBIOS_EVENT_ERROR); goto shutdown; } /* Ignore any incoming packets from myself... */ ipaddr.a_ipv4 = addr->sin.sin_addr.s_addr; ipaddr.a_family = AF_INET; if (smb_nic_is_local(&ipaddr)) goto ignore; /* * Launch a netbios worker to process the received packet. */ worker_param = malloc(sizeof (worker_param_t)); if (worker_param) { pthread_t worker; pthread_attr_t tattr; worker_param->addr = addr; worker_param->buf = buf; worker_param->length = len; (void) pthread_attr_init(&tattr); (void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED); (void) pthread_create(&worker, &tattr, smb_netbios_worker, worker_param); (void) pthread_attr_destroy(&tattr); } } shutdown: smb_netbios_event(NETBIOS_EVENT_NS_STOP); smb_netbios_wait(NETBIOS_EVENT_BROWSER_STOP); if (!smb_netbios_error()) smb_netbios_name_unconfig(); (void) close(name_sock); return (NULL); } /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * 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 #include #include #include #include #include #include #include #include static void smb_netlogon_query(struct name_entry *server, char *mailbox, char *domain); static void smb_netlogon_samlogon(struct name_entry *, char *, char *, smb_sid_t *); 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 smb_better_dc(uint32_t cur_ip, uint32_t new_ip); /* * ntdomain_info * Temporary. It should be removed once NBTD is integrated. */ extern smb_ntdomain_t ntdomain_info; extern mutex_t ntdomain_mtx; extern cond_t ntdomain_cv; /* * 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(struct name_entry *server, char *domain) { smb_domain_t di; smb_sid_t *sid = NULL; int protocol = NETLOGON_PROTO_NETLOGON; if (domain == NULL || *domain == '\0') return; (void) mutex_lock(&ntdomain_mtx); (void) strlcpy(ntdomain_info.n_domain, domain, sizeof (ntdomain_info.n_domain)); (void) mutex_unlock(&ntdomain_mtx); smb_config_getdomaininfo(di.di_nbname, NULL, di.di_sid, NULL, NULL); if (smb_strcasecmp(di.di_nbname, domain, 0) == 0) { if ((sid = smb_sid_fromstr(di.di_sid)) != NULL) protocol = NETLOGON_PROTO_SAMLOGON; } if (protocol == NETLOGON_PROTO_SAMLOGON) smb_netlogon_samlogon(server, MAILSLOT_NETLOGON_SAMLOGON_RDC, domain, sid); else smb_netlogon_query(server, MAILSLOT_NETLOGON_RDC, domain); smb_sid_free(sid); } /* * 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]; smb_wchar_t unicode_src_name[SMB_PI_MAX_HOST]; 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) oemtoucs(unicode_src_name, (char *)datagram->src.name, SMB_PI_MAX_HOST, OEM_CPG_850); (void) smb_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 == NULL || primary == NULL) { syslog(LOG_ERR, "NetLogonResponse: malformed packet"); smb_msgbuf_term(&mb); return; } syslog(LOG_DEBUG, "DC Offer Domain=%s PDC=%s From=%s", domain, primary, src_name); (void) mutex_lock(&ntdomain_mtx); if (strcasecmp(domain, ntdomain_info.n_domain)) { syslog(LOG_DEBUG, "NetLogonResponse: other domain " "%s, requested %s", domain, ntdomain_info.n_domain); smb_msgbuf_term(&mb); (void) mutex_unlock(&ntdomain_mtx); return; } (void) mutex_unlock(&ntdomain_mtx); 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 (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[NETBIOS_NAME_SZ]; if (smb_getnetbiosname(hostname, sizeof (hostname)) != 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) + smb_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_sid_t *domain_sid) { smb_msgbuf_t mb; 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[NETBIOS_NAME_SZ]; syslog(LOG_DEBUG, "NetLogonSamLogonReq: %s", domain); if (smb_getnetbiosname(hostname, sizeof (hostname)) != 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); domain_sid_len = smb_sid_len(domain_sid); /* * 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) + smb_wcequiv_strlen(hostname) + 2 + smb_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, "SmbNetlogonSend"); smb_netbios_name_logf(&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, "SmbNetlogonSend: could not 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; uint32_t ipaddr; uint32_t prefer_ipaddr; char ipstr[INET_ADDRSTRLEN]; char srcip[INET_ADDRSTRLEN]; int rc; (void) inet_ntop(AF_INET, &src_ipaddr, srcip, INET_ADDRSTRLEN); rc = smb_config_getstr(SMB_CI_DOMAIN_SRV, ipstr, INET_ADDRSTRLEN); if (rc == SMBD_SMF_OK) { rc = inet_pton(AF_INET, ipstr, &prefer_ipaddr); if (rc == 0) prefer_ipaddr = 0; if (!initialized) { syslog(LOG_DEBUG, "SMB DC Preference: %s", ipstr); initialized = 1; } } (void) mutex_lock(&ntdomain_mtx); syslog(LOG_DEBUG, "DC Offer [%s]: %s [%s]", ntdomain_info.n_domain, src_name, srcip); if (ntdomain_info.n_ipaddr != 0) { if (prefer_ipaddr != 0 && prefer_ipaddr == ntdomain_info.n_ipaddr) { syslog(LOG_DEBUG, "DC for %s: %s [%s]", ntdomain_info.n_domain, src_name, srcip); (void) mutex_unlock(&ntdomain_mtx); return; } ipaddr = ntdomain_info.n_ipaddr; } else ipaddr = 0; if (smb_better_dc(ipaddr, src_ipaddr) || (prefer_ipaddr != 0 && prefer_ipaddr == src_ipaddr)) { /* set nbtd cache */ (void) strlcpy(ntdomain_info.n_name, src_name, SMB_PI_MAX_DOMAIN); ntdomain_info.n_ipaddr = src_ipaddr; (void) cond_broadcast(&ntdomain_cv); syslog(LOG_DEBUG, "DC discovered for %s: %s [%s]", ntdomain_info.n_domain, src_name, srcip); } (void) mutex_unlock(&ntdomain_mtx); } static int smb_better_dc(uint32_t cur_ip, uint32_t new_ip) { smb_inaddr_t ipaddr; /* * If we don't have any current DC, * then use the new one of course. */ if (cur_ip == 0) return (1); /* * see if there is a DC in the * same subnet */ ipaddr.a_family = AF_INET; ipaddr.a_ipv4 = cur_ip; if (smb_nic_is_same_subnet(&ipaddr)) return (0); ipaddr.a_family = AF_INET; ipaddr.a_ipv4 = new_ip; if (smb_nic_is_same_subnet(&ipaddr)) return (1); /* * Otherwise, just keep the old one. */ return (0); }