2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A
2N/A/*
2N/A * Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A/*
2N/A * Client NDR RPC interface.
2N/A */
2N/A
2N/A#include <sys/types.h>
2N/A#include <sys/fcntl.h>
2N/A#include <sys/tzfile.h>
2N/A#include <errno.h>
2N/A#include <time.h>
2N/A#include <string.h>
2N/A#include <strings.h>
2N/A#include <assert.h>
2N/A#include <thread.h>
2N/A#include <unistd.h>
2N/A#include <syslog.h>
2N/A#include <synch.h>
2N/A#include <pthread.h>
2N/A#include <netsmb/libsmbfs.h>
2N/A#include <smbsrv/libsmb.h>
2N/A#include <smbsrv/libntsvcs.h>
2N/A#include <smbsrv/ndl/srvsvc.ndl>
2N/A#include <ntsvcs.h>
2N/A#include <smbsrv/libsmbns.h>
2N/A
2N/A#ifndef EAUTH
2N/A#define EAUTH 114
2N/A#endif
2N/A
2N/A#define NDR_XA_READSZ (16 * 1024)
2N/A
2N/Atypedef struct ndr_svinfo {
2N/A list_node_t svi_lnd;
2N/A time_t svi_tcached;
2N/A char svi_server[MAXNAMELEN];
2N/A char svi_domain[MAXNAMELEN];
2N/A srvsvc_server_info_t svi_svinfo;
2N/A} ndr_svinfo_t;
2N/A
2N/A/*
2N/A * Server info cache is protected by readers/writer lock. Currently, server
2N/A * info cache is used to hold information about domain controllers.
2N/A *
2N/A * Writers: Both DC discovery and failover threads make SRVSVC NetServerGetInfo
2N/A * request to obtain information about the selected DC. On success,
2N/A * ndr_svinfo_update() is called to update the cache.
2N/A * NOTE: this cache is updated every 20 minutes due to the periodic
2N/A * discovery cycle or upon a successful DC failover.
2N/A * Readers: DC Monitor and door service threads that are launched to make
2N/A * non-SRVSVC MS-RPC requests call ndr_svinfo_lookup() to obtain
2N/A * information about the selected DC from the cache.
2N/A *
2N/A * In the future, if our non-SRVSVC MS-RPC clients ever need to talk to
2N/A * remote servers other than domain controllers, then entries for those remote
2N/A * servers can be added by the door service threads which are launched to
2N/A * process the MS-RPC requests.
2N/A */
2N/Atypedef struct ndr_svlist {
2N/A list_t svl_list;
2N/A rwlock_t svl_lock;
2N/A boolean_t svl_init;
2N/A} ndr_svlist_t;
2N/A
2N/Astatic ndr_svlist_t ndr_svlist;
2N/A
2N/Astatic void ndr_rpc_init(void);
2N/Astatic void ndr_rpc_fini(void);
2N/A
2N/Astatic int ndr_xa_init(ndr_client_t *, ndr_xa_t *);
2N/Astatic int ndr_xa_exchange(ndr_client_t *, ndr_xa_t *);
2N/Astatic int ndr_xa_read(ndr_client_t *, ndr_xa_t *);
2N/Astatic void ndr_xa_preserve(ndr_client_t *, ndr_xa_t *);
2N/Astatic void ndr_xa_destruct(ndr_client_t *, ndr_xa_t *);
2N/Astatic void ndr_xa_release(ndr_client_t *);
2N/A
2N/Astatic void ndr_rpc_uncgen(const char *, const char *, char *, size_t);
2N/Astatic int ndr_svinfo_lookup(const char *, const char *,
2N/A srvsvc_server_info_t *);
2N/Astatic boolean_t ndr_svinfo_match(const char *, const char *, const
2N/A ndr_svinfo_t *);
2N/A
2N/Astatic mutex_t libntsvcs_mutex;
2N/Astatic boolean_t initialized;
2N/A
2N/A/* Bindings to remote MS-RPC services are now serialized on bind_mutex. */
2N/Astatic mutex_t bind_mutex;
2N/A
2N/A/*
2N/A * All NDR RPC service initialization is invoked from here.
2N/A */
2N/Avoid
2N/Antsvcs_init(void)
2N/A{
2N/A (void) mutex_lock(&libntsvcs_mutex);
2N/A
2N/A if (!initialized) {
2N/A smb_ipc_init();
2N/A smb_domain_init();
2N/A ndr_rpc_init();
2N/A srvsvc_initialize();
2N/A wkssvc_initialize();
2N/A lsarpc_initialize();
2N/A netr_initialize();
2N/A dssetup_initialize();
2N/A samr_initialize();
2N/A svcctl_initialize();
2N/A winreg_initialize();
2N/A logr_initialize();
2N/A msgsvcsend_initialize();
2N/A netdfs_initialize();
2N/A
2N/A initialized = B_TRUE;
2N/A }
2N/A
2N/A (void) mutex_unlock(&libntsvcs_mutex);
2N/A}
2N/A
2N/Avoid
2N/Antsvcs_fini(void)
2N/A{
2N/A (void) mutex_lock(&libntsvcs_mutex);
2N/A
2N/A if (initialized) {
2N/A svcctl_finalize();
2N/A logr_finalize();
2N/A netdfs_finalize();
2N/A ndr_rpc_fini();
2N/A
2N/A initialized = B_FALSE;
2N/A }
2N/A
2N/A (void) mutex_unlock(&libntsvcs_mutex);
2N/A}
2N/A
2N/A/*
2N/A * Initialize the RPC client interface: create the server info cache.
2N/A */
2N/Astatic void
2N/Andr_rpc_init(void)
2N/A{
2N/A (void) rw_wrlock(&ndr_svlist.svl_lock);
2N/A
2N/A if (!ndr_svlist.svl_init) {
2N/A list_create(&ndr_svlist.svl_list, sizeof (ndr_svinfo_t),
2N/A offsetof(ndr_svinfo_t, svi_lnd));
2N/A ndr_svlist.svl_init = B_TRUE;
2N/A }
2N/A
2N/A (void) rw_unlock(&ndr_svlist.svl_lock);
2N/A}
2N/A
2N/A/*
2N/A * Terminate the RPC client interface: flush and destroy the server info
2N/A * cache.
2N/A */
2N/Astatic void
2N/Andr_rpc_fini(void)
2N/A{
2N/A ndr_svinfo_t *svi;
2N/A
2N/A (void) rw_wrlock(&ndr_svlist.svl_lock);
2N/A
2N/A if (ndr_svlist.svl_init) {
2N/A while ((svi = list_head(&ndr_svlist.svl_list)) != NULL) {
2N/A list_remove(&ndr_svlist.svl_list, svi);
2N/A free(svi->svi_svinfo.sv_name);
2N/A free(svi->svi_svinfo.sv_comment);
2N/A free(svi);
2N/A }
2N/A
2N/A list_destroy(&ndr_svlist.svl_list);
2N/A ndr_svlist.svl_init = B_FALSE;
2N/A }
2N/A
2N/A (void) rw_unlock(&ndr_svlist.svl_lock);
2N/A}
2N/A
2N/A/*
2N/A * Returns Kerberos realm from the passed domain name.
2N/A * Realm is obtained from the resolver by comparing the passed domain name with
2N/A * - the resolver domain name or
2N/A * - the first part of the resolver domain name.
2N/A * Caller should free memory allocated to the returned realm.
2N/A */
2N/Astatic char *
2N/Andr_rpc_get_realm(char *domain)
2N/A{
2N/A char buf[MAXHOSTNAMELEN];
2N/A char *dc_realm, *p;
2N/A
2N/A if (smb_getdomainname_resolv(buf, MAXHOSTNAMELEN) != 0)
2N/A return (NULL);
2N/A
2N/A if ((dc_realm = smb_krb5_domain2realm(buf)) == NULL)
2N/A return (NULL);
2N/A
2N/A if (smb_strcasecmp(domain, buf, 0) != 0) {
2N/A if ((p = strchr(buf, '.')) != NULL)
2N/A *p = '\0';
2N/A
2N/A if (smb_strcasecmp(domain, buf, 0) != 0) {
2N/A free(dc_realm);
2N/A return (NULL);
2N/A }
2N/A }
2N/A
2N/A return (dc_realm);
2N/A}
2N/A
2N/A/*
2N/A * This functions sets up Kerberos credentials for authenticated outbound
2N/A * requests to the domain controller. If the credentials cannot be acquired,
2N/A * smb_kinit() function is called to establish credentials.
2N/A *
2N/A * Returns 0 if username is anonymous, setting cred cache or kinit fails.
2N/A * Returns -1 in case of errors.
2N/A */
2N/Astatic int
2N/Andr_rpc_setup_kerberos(char *username, char *realm)
2N/A{
2N/A char *principal = NULL;
2N/A uchar_t passwd[SMB_IPC_MAXPWDLEN];
2N/A int rc = 0;
2N/A char sam_acct[NETBIOS_NAME_SZ];
2N/A
2N/A if (strcmp(username, "") == 0)
2N/A return (0);
2N/A
2N/A if (smb_is_samaccount(username)) {
2N/A bzero(&sam_acct, NETBIOS_NAME_SZ);
2N/A if (smb_getsamaccount(sam_acct, NETBIOS_NAME_SZ) != 0)
2N/A return (-1);
2N/A
2N/A if (asprintf(&principal, "%s@%s", sam_acct, realm) == -1)
2N/A return (-1);
2N/A rc = smb_kinit(principal, NULL);
2N/A } else {
2N/A if (smb_krb5_ccache_set(SMB_KRB5_CCACHE4USER) != 0)
2N/A return (0);
2N/A
2N/A smb_ipc_get_passwd(passwd, SMB_IPC_MAXPWDLEN, B_TRUE);
2N/A if (asprintf(&principal, "%s@%s", username, realm) == -1)
2N/A return (-1);
2N/A rc = smb_kinit(principal, (char *)passwd);
2N/A }
2N/A
2N/A if (rc != 0)
2N/A syslog(LOG_DEBUG, "ndr_rpc_bind: kinit failed(%s)", principal);
2N/A
2N/A free(principal);
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Setup libsmbfs keychain for authenticating the connection.
2N/A * Keychain setup uses NTLM/SSP authentication.
2N/A */
2N/Astatic int
2N/Andr_rpc_setup_keychain(char *username, char *domain)
2N/A{
2N/A uchar_t nthash[SMBAUTH_HASH_SZ];
2N/A int rc;
2N/A
2N/A smbfs_set_default_domain(domain);
2N/A smbfs_set_default_user(username);
2N/A smb_ipc_get_passwd(nthash, sizeof (nthash), B_FALSE);
2N/A
2N/A rc = smbfs_keychain_addhash((uid_t)-1, domain, username, NULL, nthash);
2N/A return (rc);
2N/A}
2N/A
2N/A/*
2N/A * This call must be made to initialize an RPC client structure and bind
2N/A * to the remote service before any RPCs can be exchanged with that service.
2N/A *
2N/A * The mlsvc_handle_t is a wrapper that is used to associate an RPC handle
2N/A * with the client context for an instance of the interface. The handle
2N/A * is zeroed to ensure that it doesn't look like a valid handle -
2N/A * handle content is provided by the remove service.
2N/A *
2N/A * The client points to this top-level handle so that we know when to
2N/A * unbind and teardown the connection. As each handle is initialized it
2N/A * will inherit a reference to the client context.
2N/A *
2N/A * The server name must be resolvable by the smb/client, which will extract
2N/A * it from the UNC path and use it to open a socket to the server.
2N/A * Using the NetBIOS name may not always work since there is no guarantee
2N/A * that NetBIOS name resolution will be available.
2N/A */
2N/Aint
2N/Andr_rpc_bind(mlsvc_handle_t *handle, char *server, char *domain,
2N/A char *username, const char *service)
2N/A{
2N/A char uncpath[MAXPATHLEN];
2N/A ndr_client_t *clnt;
2N/A ndr_service_t *svc;
2N/A srvsvc_server_info_t svinfo;
2N/A const char *errmsg;
2N/A int fid;
2N/A int rc;
2N/A uint32_t cflag;
2N/A char *realm;
2N/A smbfs_fh_ctx_t fh_ctx;
2N/A pthread_t self = pthread_self();
2N/A
2N/A if (handle == NULL || server == NULL || domain == NULL)
2N/A return (-1);
2N/A
2N/A (void) mutex_lock(&bind_mutex);
2N/A if ((svc = ndr_svc_lookup_name(service)) == NULL) {
2N/A (void) mutex_unlock(&bind_mutex);
2N/A return (-1);
2N/A }
2N/A
2N/A if (username == NULL) {
2N/A /* Anonymous connection. */
2N/A username = "";
2N/A }
2N/A
2N/A cflag = (smb_ipc_get_ctx()) ? SMBFS_STANDALONE : SMBFS_OUTBOUNDDC;
2N/A realm = ndr_rpc_get_realm(domain);
2N/A smbfs_fh_ctx_create(&fh_ctx, cflag, realm);
2N/A
2N/A /* Setup Kerberos credentials for libsmbfs */
2N/A if ((fh_ctx.sf_realm != NULL) && (fh_ctx.sf_cflag & SMBFS_OUTBOUNDDC)) {
2N/A rc = ndr_rpc_setup_kerberos(username, fh_ctx.sf_realm);
2N/A if (rc != 0) {
2N/A syslog(LOG_NOTICE, "ndr_rpc_bind[tid=%d] %s@%s:"
2N/A " kerberos setup failed",
2N/A self, username, fh_ctx.sf_realm);
2N/A smbfs_fh_ctx_destroy(&fh_ctx);
2N/A (void) mutex_unlock(&bind_mutex);
2N/A return (-1);
2N/A }
2N/A }
2N/A
2N/A /* Setup Keychain for libsmbfs */
2N/A rc = ndr_rpc_setup_keychain(username, domain);
2N/A if (rc != 0) {
2N/A syslog(LOG_NOTICE,
2N/A "ndr_rpc_bind[tid=%d] %s %s: keychain add failed",
2N/A self, domain, username);
2N/A smbfs_fh_ctx_destroy(&fh_ctx);
2N/A (void) mutex_unlock(&bind_mutex);
2N/A return (-1);
2N/A }
2N/A
2N/A /*
2N/A * Set the default based on the assumption that most
2N/A * servers will be Windows 2000 or later.
2N/A * Don't lookup the svinfo if this is a SRVSVC request
2N/A * because the SRVSVC is used to get the server info.
2N/A * None of the SRVSVC calls depend on the server info.
2N/A */
2N/A bzero(&svinfo, sizeof (srvsvc_server_info_t));
2N/A svinfo.sv_platform_id = SV_PLATFORM_ID_NT;
2N/A svinfo.sv_version_major = 5;
2N/A svinfo.sv_version_minor = 0;
2N/A svinfo.sv_type = SV_TYPE_DEFAULT;
2N/A svinfo.sv_os = NATIVE_OS_WIN2000;
2N/A
2N/A if (strcasecmp(service, "SRVSVC") != 0) {
2N/A /*
2N/A * It's rare that the lookup could fail since DC discovery
2N/A * and failover services are responsible for creating
2N/A * entries for the currently selected DC at service startup
2N/A * and the previously selected DC where failures have been
2N/A * reported, respectively. But it could happen when the
2N/A * machine password stored locally no longer matches with
2N/A * what stored in Active Directory.
2N/A */
2N/A if (ndr_svinfo_lookup(server, domain, &svinfo) != 0) {
2N/A (void) mutex_unlock(&bind_mutex);
2N/A if (srvsvc_net_server_getinfo(server, domain, &svinfo)
2N/A == 0)
2N/A (void) ndr_svinfo_update(server, domain,
2N/A &svinfo);
2N/A (void) mutex_lock(&bind_mutex);
2N/A }
2N/A }
2N/A
2N/A if ((clnt = calloc(1, sizeof (ndr_client_t))) == NULL) {
2N/A smbfs_fh_ctx_destroy(&fh_ctx);
2N/A (void) mutex_unlock(&bind_mutex);
2N/A return (-1);
2N/A }
2N/A
2N/A ndr_rpc_uncgen(server, svc->endpoint, uncpath, MAXPATHLEN);
2N/A
2N/A if ((fid = smbfs_fh_open(uncpath, O_RDWR, &fh_ctx)) < 0) {
2N/A rc = errno;
2N/A switch (rc) {
2N/A case ENOENT:
2N/A errmsg = "server does not support this named pipe";
2N/A break;
2N/A case ENODATA:
2N/A errmsg = "unable to resolve server name";
2N/A break;
2N/A case EAUTH:
2N/A errmsg = "smb/client authentication failed";
2N/A break;
2N/A default:
2N/A errmsg = strerror(rc);
2N/A break;
2N/A }
2N/A
2N/A syslog(LOG_NOTICE, "ndr_rpc_bind[tid=%d]: %s: %s (%d)",
2N/A self, uncpath, errmsg, rc);
2N/A free(clnt);
2N/A smbfs_fh_ctx_destroy(&fh_ctx);
2N/A (void) mutex_unlock(&bind_mutex);
2N/A return (-1);
2N/A }
2N/A
2N/A smbfs_fh_ctx_destroy(&fh_ctx);
2N/A bzero(clnt, sizeof (ndr_client_t));
2N/A clnt->handle = &handle->handle;
2N/A clnt->fid = fid;
2N/A
2N/A ndr_svc_binding_pool_init(&clnt->binding_list,
2N/A clnt->binding_pool, NDR_N_BINDING_POOL);
2N/A
2N/A clnt->xa_init = ndr_xa_init;
2N/A clnt->xa_exchange = ndr_xa_exchange;
2N/A clnt->xa_read = ndr_xa_read;
2N/A clnt->xa_preserve = ndr_xa_preserve;
2N/A clnt->xa_destruct = ndr_xa_destruct;
2N/A clnt->xa_release = ndr_xa_release;
2N/A
2N/A bzero(&handle->handle, sizeof (ndr_hdid_t));
2N/A handle->clnt = clnt;
2N/A bcopy(&svinfo, &handle->svinfo, sizeof (srvsvc_server_info_t));
2N/A
2N/A if (ndr_rpc_get_heap(handle) == NULL) {
2N/A (void) smbfs_fh_close(fid);
2N/A free(clnt);
2N/A (void) mutex_unlock(&bind_mutex);
2N/A return (-1);
2N/A }
2N/A
2N/A rc = ndr_clnt_bind(clnt, service, &clnt->binding);
2N/A if (NDR_DRC_IS_FAULT(rc)) {
2N/A syslog(LOG_NOTICE, "ndr_rpc_bind[tid=%d]: %s: bind failed "
2N/A "(0x%08x)", self, uncpath, rc);
2N/A (void) smbfs_fh_close(fid);
2N/A ndr_heap_destroy(clnt->heap);
2N/A free(clnt);
2N/A handle->clnt = NULL;
2N/A (void) mutex_unlock(&bind_mutex);
2N/A return (-1);
2N/A }
2N/A
2N/A (void) mutex_unlock(&bind_mutex);
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Check the errno to determine whether the binding failure is caused
2N/A * by the the selected DC being unreachable.
2N/A *
2N/A * ENODATA
2N/A * Solaris SMB client sets ENODATA upon name->IP resolution failures.
2N/A * See $(SRC)/lib/libsmbfs/smb/ctx.c.
2N/A *
2N/A * ETIME
2N/A * Solaris SMB client sets ETIME if the data doesn't arrive before the
2N/A * timeout. See nb_ssn_pollin().
2N/A *
2N/A * EPROTO
2N/A * Solaris SMB client sets EPROTO when it fails to send or receive NetBIOS
2N/A * message in certain cases. See $(SRC)/lib/libsmbfs/smb/nb_ssn.c.
2N/A *
2N/A * NOTE: this function relies on the errno set by SMB client API to determine
2N/A * whether a remote server is reachable or not. Since the errno(s) are not a
2N/A * committed interface and can, and most likely will change in the future,
2N/A * any changes to the SMB client APIs that are consumed by ndr_rpc_bind() today
2N/A * will need to regression tested the DC failover implementation and the
2N/A * following errno(s) should be updated accordingly.
2N/A */
2N/Aboolean_t
2N/Antsvcs_srv_down(int err)
2N/A{
2N/A switch (err) {
2N/A case EBUSY: /* 16 */
2N/A case ENODATA: /* 61 */
2N/A case ETIME: /* 62 */
2N/A case EPROTO: /* 71 */
2N/A case ENETUNREACH: /* 128 */
2N/A case ENETRESET: /* 129 */
2N/A case ECONNABORTED: /* 130 */
2N/A case ECONNRESET: /* 131 */
2N/A case ETIMEDOUT: /* 145 */
2N/A case EHOSTDOWN: /* 147 */
2N/A case EHOSTUNREACH: /* 148 */
2N/A return (B_TRUE);
2N/A
2N/A default:
2N/A return (B_FALSE);
2N/A }
2N/A}
2N/A
2N/A/*
2N/A * The server parameter is expected to be a fully qualified hostname, hostname,
2N/A * NetBIOS name or an IP address.
2N/A */
2N/Astatic void
2N/Andr_rpc_uncgen(const char *server, const char *endpoint, char *buf, size_t len)
2N/A{
2N/A const char *ep;
2N/A char host[MAXHOSTNAMELEN];
2N/A
2N/A ep = endpoint;
2N/A ep += strspn(endpoint, "\\");
2N/A
2N/A if (smb_strcasecmp(ep, "PIPE", 4) == 0) {
2N/A ep += 4;
2N/A ep += strspn(ep, "\\");
2N/A }
2N/A
2N/A if (*server == '\0') {
2N/A syslog(LOG_NOTICE, "ndr_rpc_uncgen: %s: no server", endpoint);
2N/A (void) strlcpy(host, ".", MAXHOSTNAMELEN);
2N/A } else {
2N/A (void) strlcpy(host, server, MAXHOSTNAMELEN);
2N/A }
2N/A
2N/A (void) snprintf(buf, len, "\\\\%s\\PIPE\\%s", host, ep);
2N/A}
2N/A
2N/A/*
2N/A * Unbind and close the pipe to an RPC service.
2N/A *
2N/A * If the heap has been preserved we need to go through an xa release.
2N/A * The heap is preserved during an RPC call because that's where data
2N/A * returned from the server is stored.
2N/A *
2N/A * Otherwise we destroy the heap directly.
2N/A */
2N/Avoid
2N/Andr_rpc_unbind(mlsvc_handle_t *handle)
2N/A{
2N/A ndr_client_t *clnt = handle->clnt;
2N/A
2N/A if (clnt->heap_preserved)
2N/A ndr_clnt_free_heap(clnt);
2N/A else
2N/A ndr_heap_destroy(clnt->heap);
2N/A
2N/A (void) smbfs_fh_close(clnt->fid);
2N/A free(clnt);
2N/A bzero(handle, sizeof (mlsvc_handle_t));
2N/A}
2N/A
2N/A/*
2N/A * Call the RPC function identified by opnum. The remote service is
2N/A * identified by the handle, which should have been initialized by
2N/A * ndr_rpc_bind.
2N/A *
2N/A * If the RPC call is successful (returns 0), the caller must call
2N/A * ndr_rpc_release to release the heap. Otherwise, we release the
2N/A * heap here.
2N/A */
2N/Aint
2N/Andr_rpc_call(mlsvc_handle_t *handle, int opnum, void *params)
2N/A{
2N/A ndr_client_t *clnt = handle->clnt;
2N/A int rc;
2N/A
2N/A if (ndr_rpc_get_heap(handle) == NULL)
2N/A return (-1);
2N/A
2N/A rc = ndr_clnt_call(clnt->binding, opnum, params);
2N/A
2N/A /*
2N/A * Always clear the nonull flag to ensure
2N/A * it is not applied to subsequent calls.
2N/A */
2N/A clnt->nonull = B_FALSE;
2N/A
2N/A if (NDR_DRC_IS_FAULT(rc)) {
2N/A ndr_rpc_release(handle);
2N/A return (-1);
2N/A }
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Some MSRPC services declare an RPC binding handle based on the
2N/A * server's NetBIOS name prefixed (UNC style) by two backslashes.
2N/A * The NetBIOS name is derived from the server's hostname.
2N/A * The services are inconsistent on handle validation by the server.
2N/A *
2N/A * The RPC binding handle may be explicitly declared as a handle
2N/A * in the IDL (as shown below) or it may simply appear as a regular
2N/A * wchar_t parameter to an RPC.
2N/A *
2N/A * typedef [handle] wchar_t *RPC_HANDLE;
2N/A */
2N/Avoid
2N/Andr_rpc_format_nbhandle(const char *server, char *buf, size_t buflen)
2N/A{
2N/A char nbname[NETBIOS_NAME_SZ];
2N/A char *p;
2N/A
2N/A assert(buflen >= NDR_BIND_NBNAME_SZ);
2N/A
2N/A (void) strlcpy(nbname, server, NETBIOS_NAME_SZ);
2N/A
2N/A if ((p = strchr(nbname, '.')) != NULL)
2N/A *p = '\0';
2N/A
2N/A (void) smb_strupr(nbname);
2N/A (void) snprintf(buf, buflen, "\\\\%s", nbname);
2N/A}
2N/A
2N/Avoid *
2N/Andr_rpc_derive_nbhandle(mlsvc_handle_t *handle, const char *server)
2N/A{
2N/A char *nbhandle;
2N/A
2N/A if ((nbhandle = ndr_rpc_malloc(handle, NDR_BIND_NBNAME_SZ)) == NULL)
2N/A return (NULL);
2N/A
2N/A ndr_rpc_format_nbhandle(server, nbhandle, NDR_BIND_NBNAME_SZ);
2N/A return (nbhandle);
2N/A}
2N/A
2N/A/*
2N/A * Outgoing strings should not be null terminated.
2N/A */
2N/Avoid
2N/Andr_rpc_set_nonull(mlsvc_handle_t *handle)
2N/A{
2N/A handle->clnt->nonull = B_TRUE;
2N/A}
2N/A
2N/A/*
2N/A * Return a reference to the server info.
2N/A */
2N/Aconst srvsvc_server_info_t *
2N/Andr_rpc_server_info(mlsvc_handle_t *handle)
2N/A{
2N/A return (&handle->svinfo);
2N/A}
2N/A
2N/A/*
2N/A * Return the RPC server OS level.
2N/A */
2N/Auint32_t
2N/Andr_rpc_server_os(mlsvc_handle_t *handle)
2N/A{
2N/A return (handle->svinfo.sv_os);
2N/A}
2N/A
2N/A/*
2N/A * Get the session key from a bound RPC client handle.
2N/A *
2N/A * The key returned is the 16-byte "user session key"
2N/A * established by the underlying authentication protocol
2N/A * (either Kerberos or NTLM). This key is needed for
2N/A * SAM RPC calls such as SamrSetInformationUser, etc.
2N/A * See [MS-SAMR] sections: 2.2.3.3, 2.2.7.21, 2.2.7.25.
2N/A *
2N/A * The RPC endpoint must be bound when this is called
2N/A * (so that clnt->fid is an open named pipe)
2N/A *
2N/A * Returns zero (success) or an errno.
2N/A */
2N/Aint
2N/Andr_rpc_get_ssnkey(mlsvc_handle_t *handle,
2N/A unsigned char *ssn_key, size_t len)
2N/A{
2N/A ndr_client_t *clnt = handle->clnt;
2N/A int rc;
2N/A
2N/A if (clnt == NULL)
2N/A return (EINVAL);
2N/A
2N/A rc = smbfs_fh_getssnkey(clnt->fid, ssn_key, len);
2N/A return (rc);
2N/A}
2N/A
2N/Avoid *
2N/Andr_rpc_malloc(mlsvc_handle_t *handle, size_t size)
2N/A{
2N/A ndr_heap_t *heap;
2N/A
2N/A if ((heap = ndr_rpc_get_heap(handle)) == NULL)
2N/A return (NULL);
2N/A
2N/A return (ndr_heap_malloc(heap, size));
2N/A}
2N/A
2N/Andr_heap_t *
2N/Andr_rpc_get_heap(mlsvc_handle_t *handle)
2N/A{
2N/A ndr_client_t *clnt = handle->clnt;
2N/A
2N/A if (clnt->heap == NULL)
2N/A clnt->heap = ndr_heap_create();
2N/A
2N/A return (clnt->heap);
2N/A}
2N/A
2N/A/*
2N/A * Must be called by RPC clients to free the heap after a successful RPC
2N/A * call, i.e. ndr_rpc_call returned 0. The caller should take a copy
2N/A * of any data returned by the RPC prior to calling this function because
2N/A * returned data is in the heap.
2N/A */
2N/Avoid
2N/Andr_rpc_release(mlsvc_handle_t *handle)
2N/A{
2N/A ndr_client_t *clnt = handle->clnt;
2N/A
2N/A if (clnt->heap_preserved)
2N/A ndr_clnt_free_heap(clnt);
2N/A else
2N/A ndr_heap_destroy(clnt->heap);
2N/A
2N/A clnt->heap = NULL;
2N/A}
2N/A
2N/A/*
2N/A * Returns true if the handle is null.
2N/A * Otherwise returns false.
2N/A */
2N/Aboolean_t
2N/Andr_is_null_handle(mlsvc_handle_t *handle)
2N/A{
2N/A static ndr_hdid_t zero_handle;
2N/A
2N/A if (handle == NULL || handle->clnt == NULL)
2N/A return (B_TRUE);
2N/A
2N/A if (!memcmp(&handle->handle, &zero_handle, sizeof (ndr_hdid_t)))
2N/A return (B_TRUE);
2N/A
2N/A return (B_FALSE);
2N/A}
2N/A
2N/A/*
2N/A * Returns true if the handle is the top level bind handle.
2N/A * Otherwise returns false.
2N/A */
2N/Aboolean_t
2N/Andr_is_bind_handle(mlsvc_handle_t *handle)
2N/A{
2N/A return (handle->clnt->handle == &handle->handle);
2N/A}
2N/A
2N/A/*
2N/A * Pass the client reference from parent to child.
2N/A */
2N/Avoid
2N/Andr_inherit_handle(mlsvc_handle_t *child, mlsvc_handle_t *parent)
2N/A{
2N/A child->clnt = parent->clnt;
2N/A bcopy(&parent->svinfo, &child->svinfo, sizeof (srvsvc_server_info_t));
2N/A}
2N/A
2N/Avoid
2N/Andr_rpc_status(mlsvc_handle_t *handle, int opnum, DWORD status)
2N/A{
2N/A ndr_service_t *svc;
2N/A char *name = "NDR RPC";
2N/A char *s = "unknown";
2N/A
2N/A switch (NT_SC_SEVERITY(status)) {
2N/A case NT_STATUS_SEVERITY_SUCCESS:
2N/A s = "success";
2N/A break;
2N/A case NT_STATUS_SEVERITY_INFORMATIONAL:
2N/A s = "info";
2N/A break;
2N/A case NT_STATUS_SEVERITY_WARNING:
2N/A s = "warning";
2N/A break;
2N/A case NT_STATUS_SEVERITY_ERROR:
2N/A if (status == NT_STATUS_NONE_MAPPED)
2N/A s = "debug";
2N/A else
2N/A s = "error";
2N/A break;
2N/A }
2N/A
2N/A if (handle) {
2N/A svc = handle->clnt->binding->service;
2N/A name = svc->name;
2N/A }
2N/A
2N/A smb_tracef("%s[0x%02x]: %s: %s (0x%08x)",
2N/A name, opnum, s, xlate_nt_status(status), status);
2N/A}
2N/A
2N/A/*
2N/A * The following functions provide the client callback interface.
2N/A * If the caller hasn't provided a heap, create one here.
2N/A */
2N/Astatic int
2N/Andr_xa_init(ndr_client_t *clnt, ndr_xa_t *mxa)
2N/A{
2N/A ndr_stream_t *recv_nds = &mxa->recv_nds;
2N/A ndr_stream_t *send_nds = &mxa->send_nds;
2N/A ndr_heap_t *heap = clnt->heap;
2N/A int rc;
2N/A
2N/A if (heap == NULL) {
2N/A if ((heap = ndr_heap_create()) == NULL)
2N/A return (-1);
2N/A
2N/A clnt->heap = heap;
2N/A }
2N/A
2N/A mxa->heap = heap;
2N/A
2N/A rc = nds_initialize(send_nds, 0, NDR_MODE_CALL_SEND, heap);
2N/A if (rc == 0)
2N/A rc = nds_initialize(recv_nds, NDR_PDU_SIZE_HINT_DEFAULT,
2N/A NDR_MODE_RETURN_RECV, heap);
2N/A
2N/A if (rc != 0) {
2N/A nds_destruct(&mxa->recv_nds);
2N/A nds_destruct(&mxa->send_nds);
2N/A ndr_heap_destroy(mxa->heap);
2N/A mxa->heap = NULL;
2N/A clnt->heap = NULL;
2N/A return (-1);
2N/A }
2N/A
2N/A if (clnt->nonull)
2N/A NDS_SETF(send_nds, NDS_F_NONULL);
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * This is the entry pointy for an RPC client call exchange with
2N/A * a server, which will result in an SmbTransact request.
2N/A * On success, the receive stream pdu_size indicates the number
2N/A * of bytes received.
2N/A *
2N/A * TBD: handling overflow.
2N/A */
2N/Astatic int
2N/Andr_xa_exchange(ndr_client_t *clnt, ndr_xa_t *mxa)
2N/A{
2N/A ndr_stream_t *recv_nds = &mxa->recv_nds;
2N/A ndr_stream_t *send_nds = &mxa->send_nds;
2N/A int nbytes;
2N/A int overflow;
2N/A int rc;
2N/A
2N/A nbytes = recv_nds->pdu_max_size;
2N/A
2N/A if (nbytes > NDR_DEFAULT_FRAGSZ)
2N/A nbytes = NDR_DEFAULT_FRAGSZ;
2N/A
2N/A rc = smbfs_fh_xactnp(clnt->fid,
2N/A send_nds->pdu_size, (char *)send_nds->pdu_base_offset,
2N/A &nbytes, (char *)recv_nds->pdu_base_offset, &overflow);
2N/A
2N/A if (rc) {
2N/A syslog(LOG_DEBUG, "ndr_xa_exchange failed: %d", rc);
2N/A recv_nds->pdu_size = 0;
2N/A return (-1);
2N/A }
2N/A
2N/A recv_nds->pdu_size = nbytes;
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * This entry point will be invoked if the xa-exchange response contained
2N/A * only the first fragment of a multi-fragment response. The RPC client
2N/A * code will then make repeated xa-read requests to obtain the remaining
2N/A * fragments, which will result in SmbReadX requests.
2N/A *
2N/A * SmbReadX should return the number of bytes received, in which case we
2N/A * expand the PDU size to include the received data, or a negative error
2N/A * code.
2N/A */
2N/Astatic int
2N/Andr_xa_read(ndr_client_t *clnt, ndr_xa_t *mxa)
2N/A{
2N/A ndr_stream_t *nds = &mxa->recv_nds;
2N/A int len;
2N/A int nbytes;
2N/A
2N/A if ((len = (nds->pdu_max_size - nds->pdu_size)) < 0)
2N/A return (-1);
2N/A
2N/A if (len > NDR_XA_READSZ)
2N/A len = NDR_XA_READSZ;
2N/A
2N/A nbytes = smbfs_fh_read(clnt->fid,
2N/A (char *)nds->pdu_base_offset + nds->pdu_size, len, 0);
2N/A
2N/A if (nbytes < 0) {
2N/A syslog(LOG_DEBUG, "ndr_xa_read failed: %d", errno);
2N/A return (-1);
2N/A }
2N/A
2N/A nds->pdu_size += nbytes;
2N/A
2N/A if (nds->pdu_size > nds->pdu_max_size) {
2N/A nds->pdu_size = nds->pdu_max_size;
2N/A return (-1);
2N/A }
2N/A
2N/A return (nbytes);
2N/A}
2N/A
2N/A/*
2N/A * Preserve the heap so that the client application has access to data
2N/A * returned from the server after an RPC call.
2N/A */
2N/Astatic void
2N/Andr_xa_preserve(ndr_client_t *clnt, ndr_xa_t *mxa)
2N/A{
2N/A assert(clnt->heap == mxa->heap);
2N/A
2N/A clnt->heap_preserved = B_TRUE;
2N/A mxa->heap = NULL;
2N/A}
2N/A
2N/A/*
2N/A * Dispose of the transaction streams. If the heap has not been
2N/A * preserved, we can destroy it here.
2N/A */
2N/Astatic void
2N/Andr_xa_destruct(ndr_client_t *clnt, ndr_xa_t *mxa)
2N/A{
2N/A nds_destruct(&mxa->recv_nds);
2N/A nds_destruct(&mxa->send_nds);
2N/A
2N/A if (!clnt->heap_preserved) {
2N/A ndr_heap_destroy(mxa->heap);
2N/A mxa->heap = NULL;
2N/A clnt->heap = NULL;
2N/A }
2N/A}
2N/A
2N/A/*
2N/A * Dispose of a preserved heap.
2N/A */
2N/Astatic void
2N/Andr_xa_release(ndr_client_t *clnt)
2N/A{
2N/A if (clnt->heap_preserved) {
2N/A ndr_heap_destroy(clnt->heap);
2N/A clnt->heap = NULL;
2N/A clnt->heap_preserved = B_FALSE;
2N/A }
2N/A}
2N/A
2N/A/*
2N/A * Lookup platform, type and version information about a server.
2N/A */
2N/Astatic int
2N/Andr_svinfo_lookup(const char *server, const char *domain,
2N/A srvsvc_server_info_t *svinfo)
2N/A{
2N/A ndr_svinfo_t *svi;
2N/A
2N/A (void) rw_rdlock(&ndr_svlist.svl_lock);
2N/A if (!ndr_svlist.svl_init) {
2N/A (void) rw_unlock(&ndr_svlist.svl_lock);
2N/A return (-1);
2N/A }
2N/A
2N/A svi = list_head(&ndr_svlist.svl_list);
2N/A while (svi != NULL) {
2N/A if (ndr_svinfo_match(server, domain, svi)) {
2N/A bcopy(&svi->svi_svinfo, svinfo,
2N/A sizeof (srvsvc_server_info_t));
2N/A svinfo->sv_name = NULL;
2N/A svinfo->sv_comment = NULL;
2N/A (void) rw_unlock(&ndr_svlist.svl_lock);
2N/A return (0);
2N/A }
2N/A
2N/A svi = list_next(&ndr_svlist.svl_list, svi);
2N/A }
2N/A
2N/A (void) rw_unlock(&ndr_svlist.svl_lock);
2N/A return (-1);
2N/A}
2N/A
2N/A/*
2N/A * Adding a new entry or replacing an existing entry for the
2N/A * specified server in the server info cache.
2N/A */
2N/Aint
2N/Andr_svinfo_update(const char *server, const char *domain,
2N/A const srvsvc_server_info_t *svinfo)
2N/A{
2N/A ndr_svinfo_t *svi;
2N/A
2N/A (void) rw_wrlock(&ndr_svlist.svl_lock);
2N/A if (!ndr_svlist.svl_init) {
2N/A (void) rw_unlock(&ndr_svlist.svl_lock);
2N/A return (-1);
2N/A }
2N/A
2N/A svi = list_head(&ndr_svlist.svl_list);
2N/A while (svi != NULL) {
2N/A if (ndr_svinfo_match(server, domain, svi)) {
2N/A list_remove(&ndr_svlist.svl_list, svi);
2N/A free(svi->svi_svinfo.sv_name);
2N/A free(svi->svi_svinfo.sv_comment);
2N/A free(svi);
2N/A svi = list_head(&ndr_svlist.svl_list);
2N/A continue;
2N/A }
2N/A
2N/A svi = list_next(&ndr_svlist.svl_list, svi);
2N/A }
2N/A
2N/A if ((svi = calloc(1, sizeof (ndr_svinfo_t))) == NULL) {
2N/A (void) rw_unlock(&ndr_svlist.svl_lock);
2N/A return (-1);
2N/A }
2N/A
2N/A (void) time(&svi->svi_tcached);
2N/A (void) strlcpy(svi->svi_server, server, MAXNAMELEN);
2N/A (void) strlcpy(svi->svi_domain, domain, MAXNAMELEN);
2N/A bcopy(svinfo, &svi->svi_svinfo, sizeof (srvsvc_server_info_t));
2N/A list_insert_tail(&ndr_svlist.svl_list, svi);
2N/A (void) rw_unlock(&ndr_svlist.svl_lock);
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Compare the specified server and domain against the server and domain fields
2N/A * of each cache entry, respectively . If we find a cache entry with a matching
2N/A * server name but its corresponding domain information doesn't match, it's
2N/A * likely that the specified domain and the cached domain are stored in
2N/A * different formats. Converts the domain names from the fully-qualified
2N/A * format to NetBIOS format for further comparison.
2N/A */
2N/Astatic boolean_t
2N/Andr_svinfo_match(const char *server, const char *domain,
2N/A const ndr_svinfo_t *svi)
2N/A{
2N/A char *nb_domain1, *nb_domain2, *p;
2N/A boolean_t found;
2N/A
2N/A if (smb_strcasecmp(server, svi->svi_server, 0) == 0) {
2N/A if (smb_strcasecmp(domain, svi->svi_domain, 0) == 0)
2N/A return (B_TRUE);
2N/A
2N/A if ((nb_domain1 = strdup(domain)) == NULL)
2N/A return (B_FALSE);
2N/A
2N/A if ((nb_domain2 = strdup(svi->svi_domain)) == NULL) {
2N/A free(nb_domain1);
2N/A return (B_FALSE);
2N/A }
2N/A
2N/A if ((p = strchr(nb_domain1, '.')) != NULL)
2N/A *p = '\0';
2N/A
2N/A if (strlen(nb_domain1) >= NETBIOS_NAME_SZ)
2N/A nb_domain1[NETBIOS_NAME_SZ - 1] = '\0';
2N/A
2N/A if ((p = strchr(nb_domain2, '.')) != NULL)
2N/A *p = '\0';
2N/A
2N/A if (strlen(nb_domain2) >= NETBIOS_NAME_SZ)
2N/A nb_domain2[NETBIOS_NAME_SZ - 1] = '\0';
2N/A
2N/A found = (smb_strcasecmp(nb_domain1, nb_domain2, 0) == 0);
2N/A free(nb_domain1);
2N/A free(nb_domain2);
2N/A return (found);
2N/A }
2N/A
2N/A return (B_FALSE);
2N/A}
2N/A
2N/A/*
2N/A * Compare the time here with the remote time on the server
2N/A * and report clock skew.
2N/A */
2N/Aint
2N/Asrvsvc_timecheck(char *server, char *domain)
2N/A{
2N/A char hostname[MAXHOSTNAMELEN];
2N/A struct timeval dc_tv;
2N/A struct tm dc_tm;
2N/A struct tm *tm;
2N/A time_t tnow;
2N/A time_t tdiff;
2N/A int priority;
2N/A
2N/A if (srvsvc_net_remote_tod(server, domain, &dc_tv, &dc_tm) < 0) {
2N/A syslog(LOG_DEBUG, "srvsvc_net_remote_tod failed");
2N/A return (-1);
2N/A }
2N/A
2N/A tnow = time(NULL);
2N/A
2N/A if (tnow > dc_tv.tv_sec)
2N/A tdiff = (tnow - dc_tv.tv_sec) / SECSPERMIN;
2N/A else
2N/A tdiff = (dc_tv.tv_sec - tnow) / SECSPERMIN;
2N/A
2N/A if (tdiff != 0) {
2N/A (void) strlcpy(hostname, "localhost", MAXHOSTNAMELEN);
2N/A (void) gethostname(hostname, MAXHOSTNAMELEN);
2N/A
2N/A priority = (tdiff > 2) ? LOG_NOTICE : LOG_DEBUG;
2N/A syslog(priority, "DC [%s] clock skew detected: %ld minutes",
2N/A server, tdiff);
2N/A
2N/A tm = gmtime(&dc_tv.tv_sec);
2N/A syslog(priority, "%-8s UTC: %s", server, asctime(tm));
2N/A tm = gmtime(&tnow);
2N/A syslog(priority, "%-8s UTC: %s", hostname, asctime(tm));
2N/A }
2N/A
2N/A return (0);
2N/A}