From cb1d711e25ab62cc4950561a94abc9bca0363d2d Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Wed, 9 Aug 2023 20:24:52 +0200 Subject: [PATCH] s3:rpc_server/witness: add implementation based on CTDB_SRVID_IPREALLOCATED and ctdbd_all_ip_foreach() MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The design is relatively simple in the end: - We use ctdbd_all_ip_foreach() in order to build an in memory list of interfaces(ip addresses) and record if: - they are currently available or not - if they node local or not - The current list is would we use for the GetInterfaceList() call. - Register[Ex] will create an in memory structure holding a queue for pending AsyncNotify requests. - Unregister() will cancel pending AsyncNotify requests and let them return NOT_FOUND. - CTDB_SRVID_IPREALLOCATED messages will cause we refresh with ctdbd_all_ip_foreach(): - this will detect changes in the interface state and remove stale interfaces. - for each change the list of registrations is checked for a matching ip address and a RESOURCE_CHANGE will be scheduled in the queue of the registration, the started queue will trigger AsyncNotify responses - We also register the connections with ctdb in order to give other nodes a chance to generate tickle-acks for the witness tcp connections. Signed-off-by: Stefan Metzmacher Reviewed-by: Günther Deschner --- source3/rpc_server/witness/srv_witness_nt.c | 1683 ++++++++++++++++++++++++++- 1 file changed, 1672 insertions(+), 11 deletions(-) diff --git a/source3/rpc_server/witness/srv_witness_nt.c b/source3/rpc_server/witness/srv_witness_nt.c index 21bdae3a188..5f8897a260f 100644 --- a/source3/rpc_server/witness/srv_witness_nt.c +++ b/source3/rpc_server/witness/srv_witness_nt.c @@ -1,7 +1,9 @@ /* * Unix SMB/CIFS implementation. * - * Copyright (C) 2023 Stefan Metzmacher + * Copyright (C) 2012,2023 Stefan Metzmacher + * Copyright (C) 2015 Guenther Deschner + * Copyright (C) 2018 Samuel Cabrero * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,10 +20,827 @@ */ #include "includes.h" +#include "ctdbd_conn.h" +#include "ctdb/protocol/protocol.h" +#include "ctdb_srvids.h" +#include "messages.h" +#include "lib/messages_ctdb.h" +#include "lib/global_contexts.h" +#include "lib/util/util_tdb.h" +#include "lib/util/util_str_escape.h" +#include "source3/include/util_tdb.h" +#include "lib/dbwrap/dbwrap_rbt.h" +#include "lib/dbwrap/dbwrap_open.h" +#include "lib/param/param.h" +#include "lib/util/tevent_werror.h" +#include "lib/tsocket/tsocket.h" #include "librpc/rpc/dcesrv_core.h" +#include "libcli/security/security.h" +#include "librpc/gen_ndr/auth.h" +#include "librpc/gen_ndr/ndr_witness.h" #include "librpc/gen_ndr/ndr_witness_scompat.h" #include "rpc_server/rpc_server.h" +#define SWN_SERVICE_CONTEXT_HANDLE_REGISTRATION 0x01 + +struct swn_service_interface; +struct swn_service_registration; +struct swn_service_async_notify_state; + +struct swn_service_globals { + struct dcesrv_context *dce_ctx; + struct db_context *dce_conn_register; + + const char *server_global_name; + uint32_t local_vnn; + + struct { + bool valid; + uint64_t generation; + struct swn_service_interface *list; + } interfaces; + + struct { + uint32_t unused_timeout_secs; + struct swn_service_registration *list; + } registrations; +}; + +struct swn_service_interface { + struct swn_service_interface *prev, *next; + + const char *group_name; + struct samba_sockaddr addr; + enum witness_interfaceInfo_state state; + bool local_iface; + uint32_t current_vnn; + uint64_t change_generation; + uint64_t check_generation; +}; + +struct swn_service_registration { + struct swn_service_registration *prev, *next; + + struct swn_service_globals *swn; + + struct { + struct policy_handle handle; + void *ptr; + } key; + + struct { + enum witness_version version; + const char *computer_name; + } client; + + const char *net_name; + const char *share_name; + struct samba_sockaddr ip_address; + + struct { + bool triggered; + /* + * We only do ip based RESOURCE_CHANGE notifications for now + * and it means we do just one notification at a time + * and don't need to queue pending notifications. + */ + enum witness_interfaceInfo_state last_ip_state; + } change_notification; + + struct { + struct timeval create_time; + struct timeval last_time; + uint32_t unused_timeout_secs; + struct timeval expire_time; + struct tevent_timer *timer; + } usage; + + struct { + /* + * In order to let a Windows server 2022 + * correctly re-register after moving + * to a new connection, we force an + * unregistration after 5 seconds. + * + * It means the client gets WERR_NOT_FOUND + * from a pending AsyncNotify() and calls + * Unregister() (which also gets WERR_NOT_FOUND). + * Then the client calls GetInterfaceList() + * and RegisterEx() again. + */ + struct tevent_timer *timer; + } forced_unregister; + + struct { + uint32_t timeout_secs; + struct tevent_queue *queue; + struct swn_service_async_notify_state *list; + } async_notify; +}; + +static struct swn_service_globals *swn_globals = NULL; + +static int swn_service_globals_destructor(struct swn_service_globals *swn) +{ + SMB_ASSERT(swn == swn_globals); + swn_globals = NULL; + + while (swn->registrations.list != NULL) { + /* + * NO TALLOC_FREE() because of DLIST_REMOVE() + * in swn_service_registration_destructor() + */ + talloc_free(swn->registrations.list); + } + + return 0; +} + +static void swn_service_async_notify_reg_destroyed(struct swn_service_async_notify_state *state); + +static int swn_service_registration_destructor(struct swn_service_registration *reg) +{ + struct swn_service_globals *swn = reg->swn; + + tevent_queue_stop(reg->async_notify.queue); + while (reg->async_notify.list != NULL) { + swn_service_async_notify_reg_destroyed(reg->async_notify.list); + } + + DLIST_REMOVE(swn->registrations.list, reg); + reg->swn = NULL; + + /* + * make sure to drop the policy/context handle from + * the assoc_group + */ + TALLOC_FREE(reg->key.ptr); + + return 0; +} + +static void swn_service_registration_update_usage(struct swn_service_registration *reg, + struct timeval now) +{ + uint64_t expire_timeout_secs = 0; + uint64_t max_expire_timeout_secs = TIME_T_MAX; + + reg->usage.last_time = now; + + if (max_expire_timeout_secs > reg->usage.last_time.tv_sec) { + max_expire_timeout_secs -= reg->usage.last_time.tv_sec; + } else { + /* + * This should never happen unless + * a 32 bit system hits its limit + */ + max_expire_timeout_secs = 0; + } + + if (tevent_queue_length(reg->async_notify.queue) != 0) { + expire_timeout_secs += reg->async_notify.timeout_secs; + } + + expire_timeout_secs += reg->usage.unused_timeout_secs; + expire_timeout_secs = MIN(expire_timeout_secs, max_expire_timeout_secs); + + reg->usage.expire_time = timeval_add(®->usage.last_time, + expire_timeout_secs, 0); + + if (expire_timeout_secs == 0) { + /* + * No timer needed, witness v1 + * or max_expire_timeout_secs = 0 + */ + TALLOC_FREE(reg->usage.timer); + } + + if (reg->usage.timer == NULL) { + /* no timer to update */ + reg->usage.expire_time = (struct timeval) { .tv_sec = TIME_T_MAX, }; + return; + } + + tevent_update_timer(reg->usage.timer, reg->usage.expire_time); +} + +static void swn_service_registration_unused(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct swn_service_registration *reg = + talloc_get_type_abort(private_data, + struct swn_service_registration); + + reg->usage.timer = NULL; + + TALLOC_FREE(reg); +} + +static void swn_service_registration_force_unregister(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct swn_service_registration *reg = + talloc_get_type_abort(private_data, + struct swn_service_registration); + + reg->forced_unregister.timer = NULL; + + TALLOC_FREE(reg); +} + +static int swn_service_ctdb_ipreallocated(struct tevent_context *ev, + uint32_t src_vnn, uint32_t dst_vnn, + uint64_t dst_srvid, + const uint8_t *msg, size_t msglen, + void *private_data); + +static NTSTATUS swn_service_init_globals(struct dcesrv_context *dce_ctx) +{ + struct swn_service_globals *swn = NULL; + const char *realm = NULL; + const char *nbname = NULL; + int ret; + bool ok; + + if (swn_globals != NULL) { + SMB_ASSERT(swn_globals->dce_ctx == dce_ctx); + return NT_STATUS_OK; + } + + swn = talloc_zero(dce_ctx, struct swn_service_globals); + if (swn == NULL) { + return NT_STATUS_NO_MEMORY; + } + swn->dce_ctx = dce_ctx; + + swn->dce_conn_register = db_open_rbt(swn); + if (swn->dce_conn_register == NULL) { + TALLOC_FREE(swn); + return NT_STATUS_NO_MEMORY; + } + + nbname = lpcfg_netbios_name(dce_ctx->lp_ctx); + realm = lpcfg_realm(dce_ctx->lp_ctx); + if (realm != NULL && realm[0] != '\0') { + char *name = NULL; + + name = talloc_asprintf(swn, "%s.%s", nbname, realm); + if (name == NULL) { + TALLOC_FREE(swn); + return NT_STATUS_NO_MEMORY; + } + ok = strlower_m(name); + if (!ok) { + TALLOC_FREE(swn); + return NT_STATUS_INTERNAL_ERROR; + } + swn->server_global_name = name; + } else { + swn->server_global_name = talloc_strdup(swn, nbname); + if (swn->server_global_name == NULL) { + TALLOC_FREE(swn); + return NT_STATUS_NO_MEMORY; + } + } + + swn->local_vnn = get_my_vnn(); + + ret = register_with_ctdbd(messaging_ctdb_connection(), + CTDB_SRVID_IPREALLOCATED, + swn_service_ctdb_ipreallocated, + swn); + if (ret != 0) { + TALLOC_FREE(swn); + return NT_STATUS_INTERNAL_ERROR; + } + + swn->registrations.unused_timeout_secs = 30; + + talloc_set_destructor(swn, swn_service_globals_destructor); + swn_globals = swn; + return NT_STATUS_OK; +} + +static struct swn_service_interface *swn_service_interface_by_addr( + struct swn_service_globals *swn, + const struct samba_sockaddr *addr) +{ + struct swn_service_interface *iface = NULL; + + for (iface = swn->interfaces.list; iface != NULL; iface = iface->next) { + bool ok; + + ok = sockaddr_equal(&iface->addr.u.sa, &addr->u.sa); + if (ok) { + return iface; + } + } + + return NULL; +} + +static void swn_service_interface_changed(struct swn_service_globals *swn, + struct swn_service_interface *iface) +{ + struct swn_service_registration *reg = NULL; + char addr[INET6_ADDRSTRLEN] = { 0, }; + + print_sockaddr(addr, sizeof(addr), &iface->addr.u.ss); + DBG_NOTICE("addr[%s] state[%u] local_iface[%u] " + "current_vnn[%"PRIu32"] generation[%"PRIu64"][%"PRIu64"]\n", + addr, + iface->state, + iface->local_iface, + iface->current_vnn, + iface->change_generation, + iface->check_generation); + + for (reg = swn->registrations.list; reg != NULL; reg = reg->next) { + bool match; + + /* + * We only check the ip address, + * we do not make real use of the group name. + */ + + match = sockaddr_equal(®->ip_address.u.sa, + &iface->addr.u.sa); + if (!match) { + continue; + } + + if (reg->change_notification.last_ip_state + != WITNESS_STATE_UNAVAILABLE) + { + /* + * Remember the current state unless we already + * hit WITNESS_STATE_UNAVAILAVLE before we notified + * the client + */ + reg->change_notification.last_ip_state = iface->state; + } + + reg->change_notification.triggered = true; + + tevent_queue_start(reg->async_notify.queue); + } + + return; +} + +static NTSTATUS swn_service_add_or_update_interface(struct swn_service_globals *swn, + const char *group_name, + const struct samba_sockaddr *addr, + enum witness_interfaceInfo_state state, + bool local_iface, + uint32_t current_vnn) +{ + struct swn_service_interface *iface = NULL; + bool changed = false; + bool force_unavailable = false; + bool filter; + + if (addr->u.sa.sa_family != AF_INET && + addr->u.sa.sa_family != AF_INET6) + { + /* + * We only support ipv4 and ipv6 + */ + return NT_STATUS_OK; + } + + filter = is_loopback_addr(&addr->u.sa); + if (filter) { + return NT_STATUS_OK; + } + filter = is_linklocal_addr(&addr->u.ss); + if (filter) { + return NT_STATUS_OK; + } + + for (iface = swn->interfaces.list; iface != NULL; iface = iface->next) { + bool match; + + match = strequal(group_name, iface->group_name); + if (!match) { + continue; + } + + match = sockaddr_equal(&addr->u.sa, &iface->addr.u.sa); + if (!match) { + continue; + } + + break; + } + + if (iface == NULL) { + iface = talloc_zero(swn, struct swn_service_interface); + if (iface == NULL) { + return NT_STATUS_NO_MEMORY; + } + + iface->group_name = talloc_strdup(iface, group_name); + if (iface->group_name == NULL) { + TALLOC_FREE(iface); + return NT_STATUS_NO_MEMORY; + } + + iface->addr = *addr; + + iface->state = WITNESS_STATE_UNKNOWN; + iface->current_vnn = NONCLUSTER_VNN; + DLIST_ADD_END(swn->interfaces.list, iface); + + iface->change_generation = swn->interfaces.generation; + } + + if (iface->state != state) { + changed = true; + iface->state = state; + } + + if (iface->current_vnn != current_vnn) { + changed = true; + if (iface->current_vnn != NONCLUSTER_VNN) { + force_unavailable = true; + } + iface->current_vnn = current_vnn; + } + + if (iface->local_iface != local_iface) { + changed = true; + force_unavailable = true; + iface->local_iface = local_iface; + } + + iface->check_generation = swn->interfaces.generation; + + if (!changed) { + return NT_STATUS_OK; + } + + iface->change_generation = swn->interfaces.generation; + + if (force_unavailable) { + iface->state = WITNESS_STATE_UNAVAILABLE; + } + + swn_service_interface_changed(swn, iface); + + if (force_unavailable) { + iface->state = state; + } + + return NT_STATUS_OK; +}; + +static int swn_service_ctdb_all_ip_cb(uint32_t total_ip_count, + const struct sockaddr_storage *ip, + uint32_t pinned_vnn, + uint32_t current_vnn, + void *private_data) +{ + struct swn_service_globals *swn = + talloc_get_type_abort(private_data, + struct swn_service_globals); + enum witness_interfaceInfo_state state = WITNESS_STATE_UNKNOWN; + struct samba_sockaddr addr = { + .u = { + .ss = *ip, + }, + }; + NTSTATUS status; + bool local_iface = false; + + SMB_ASSERT(swn->local_vnn != NONCLUSTER_VNN); + + if (current_vnn == NONCLUSTER_VNN) { + /* + * No node hosts this address + */ + state = WITNESS_STATE_UNAVAILABLE; + } else { + state = WITNESS_STATE_AVAILABLE; + } + + if (current_vnn == swn->local_vnn || pinned_vnn == swn->local_vnn) { + local_iface = true; + } + + status = swn_service_add_or_update_interface(swn, + swn->server_global_name, + &addr, + state, + local_iface, + current_vnn); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("swn_service_add_or_update_interface() failed: %s\n", + nt_errstr(status)); + return map_errno_from_nt_status(status); + } + + return 0; +} + +static NTSTATUS swn_service_reload_interfaces(struct dcesrv_context *dce_ctx) +{ + struct swn_service_interface *iface = NULL; + struct swn_service_interface *next = NULL; + bool include_node_ips = false; + bool include_public_ips = true; + int ret; + NTSTATUS status; + + status = swn_service_init_globals(dce_ctx); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (swn_globals->interfaces.valid) { + return NT_STATUS_OK; + } + + swn_globals->interfaces.generation += 1; + + include_node_ips = lpcfg_parm_bool(dce_ctx->lp_ctx, + NULL, + "rpcd witness", + "include node ips", + include_node_ips); + include_public_ips = lpcfg_parm_bool(dce_ctx->lp_ctx, + NULL, + "rpcd witness", + "include public ips", + include_public_ips); + + ret = ctdbd_all_ip_foreach(messaging_ctdb_connection(), + include_node_ips, + include_public_ips, + swn_service_ctdb_all_ip_cb, + swn_globals); + if (ret != 0) { + return NT_STATUS_INTERNAL_ERROR; + } + + for (iface = swn_globals->interfaces.list; iface != NULL; iface = next) { + next = iface->next; + + if (iface->check_generation == swn_globals->interfaces.generation) { + continue; + } + + status = swn_service_add_or_update_interface(swn_globals, + iface->group_name, + &iface->addr, + WITNESS_STATE_UNAVAILABLE, + iface->local_iface, + iface->current_vnn); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DLIST_REMOVE(swn_globals->interfaces.list, iface); + TALLOC_FREE(iface); + } + + /* CTDB_SRVID_IPREALLOCATED is still registered */ + + swn_globals->interfaces.valid = true; + return NT_STATUS_OK; +} + +static int swn_service_ctdb_ipreallocated(struct tevent_context *ev, + uint32_t src_vnn, uint32_t dst_vnn, + uint64_t dst_srvid, + const uint8_t *msg, size_t msglen, + void *private_data) +{ + struct swn_service_globals *swn = + talloc_get_type_abort(private_data, + struct swn_service_globals); + NTSTATUS status; + + DBG_DEBUG("PID[%d] swn[%p] IPREALLOCATED\n", getpid(), swn); + + swn->interfaces.valid = false; + status = swn_service_reload_interfaces(swn->dce_ctx); + if (!NT_STATUS_IS_OK(status)) { + swn->interfaces.valid = false; + return 0; + } + + return 0; +} + +struct swn_dcesrv_connection { + struct db_context *rbt; + struct dcesrv_connection *conn; + struct samba_sockaddr cli_addr; + struct samba_sockaddr srv_addr; + char addr[INET6_ADDRSTRLEN]; +}; + +static int swn_dcesrv_connection_release_ip(struct tevent_context *ev, + uint32_t src_vnn, + uint32_t dst_vnn, + uint64_t dst_srvid, + const uint8_t *msg, + size_t msglen, + void *private_data) +{ + struct swn_dcesrv_connection *sc = + talloc_get_type_abort(private_data, + struct swn_dcesrv_connection); + struct dcesrv_connection *conn = sc->conn; + const char *ip = NULL; + const char *addr = sc->addr; + const char *p = addr; + + if (conn->terminate != NULL) { + /* avoid recursion */ + return 0; + } + + if (msglen == 0) { + return 0; + } + if (msg[msglen-1] != '\0') { + return 0; + } + + ip = (const char *)msg; + + if (strncmp("::ffff:", addr, 7) == 0) { + p = addr + 7; + } + + DBG_DEBUG("Got release IP message for %s, our address is %s\n", ip, p); + + if ((strcmp(p, ip) == 0) || ((p != addr) && strcmp(addr, ip) == 0)) { + DBG_NOTICE("Got release IP message for our IP %s - exiting immediately\n", + ip); + talloc_free(sc); + dcesrv_terminate_connection(conn, "CTDB_SRVID_RELEASE_IP"); + return EADDRNOTAVAIL; + } + + return 0; + +} + +static int swn_dcesrv_connection_destructor(struct swn_dcesrv_connection *sc) +{ + struct ctdbd_connection *cconn = messaging_ctdb_connection(); + struct dcesrv_connection *conn = sc->conn; + uintptr_t conn_ptr = (uintptr_t)conn; + NTSTATUS status; + TDB_DATA key; + + key = make_tdb_data((uint8_t *)&conn_ptr, sizeof(conn_ptr)); + + status = dbwrap_delete(sc->rbt, key); + SMB_ASSERT(NT_STATUS_IS_OK(status)); + + if (cconn == NULL) { + return 0; + } + + ctdbd_unregister_ips(cconn, + &sc->srv_addr.u.ss, + &sc->cli_addr.u.ss, + swn_dcesrv_connection_release_ip, + sc); + + return 0; +} + +static NTSTATUS dcesrv_interface_witness_register_ips(struct dcesrv_connection *conn) +{ + struct ctdbd_connection *cconn = messaging_ctdb_connection(); + struct dcesrv_context *dce_ctx = conn->dce_ctx; + const struct tsocket_address *client_address = + dcesrv_connection_get_remote_address(conn); + const struct tsocket_address *server_address = + dcesrv_connection_get_local_address(conn); + NTSTATUS status; + uintptr_t conn_ptr = (uintptr_t)conn; + struct swn_dcesrv_connection *sc = NULL; + uintptr_t sc_ptr; + const char *addr = NULL; + TDB_DATA key; + TDB_DATA val; + bool exists; + int ret; + + status = swn_service_init_globals(dce_ctx); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("swn_service_init_globals() failed: %s\n", + nt_errstr(status)); + return status; + } + + key = make_tdb_data((uint8_t *)&conn_ptr, sizeof(conn_ptr)); + + exists = dbwrap_exists(swn_globals->dce_conn_register, key); + if (exists) { + /* Already registered */ + return NT_STATUS_OK; + } + + sc = talloc_zero(conn, struct swn_dcesrv_connection); + if (sc == NULL) { + return NT_STATUS_NO_MEMORY; + } + sc->rbt = swn_globals->dce_conn_register; + sc->conn = conn; + + if (tsocket_address_is_inet(client_address, "ip")) { + ssize_t sret; + + sret = tsocket_address_bsd_sockaddr(client_address, + &sc->cli_addr.u.sa, + sizeof(sc->cli_addr.u.ss)); + if (sret == -1) { + TALLOC_FREE(sc); + return NT_STATUS_INTERNAL_ERROR; + } + sc->cli_addr.sa_socklen = sret; + } + + if (tsocket_address_is_inet(server_address, "ip")) { + ssize_t sret; + + sret = tsocket_address_bsd_sockaddr(server_address, + &sc->srv_addr.u.sa, + sizeof(sc->srv_addr.u.ss)); + if (sret == -1) { + TALLOC_FREE(sc); + return NT_STATUS_INTERNAL_ERROR; + } + sc->srv_addr.sa_socklen = sret; + } + + addr = print_sockaddr(sc->addr, sizeof(sc->addr), &sc->srv_addr.u.ss); + if (addr == NULL) { + TALLOC_FREE(sc); + return NT_STATUS_NO_MEMORY; + } + + sc_ptr = (uintptr_t)sc; + val = make_tdb_data((uint8_t *)&sc_ptr, sizeof(sc_ptr)); + + status = dbwrap_store(sc->rbt, key, val, TDB_INSERT); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(sc); + return status; + } + + talloc_set_destructor(sc, swn_dcesrv_connection_destructor); + + + ret = ctdbd_register_ips(cconn, + &sc->srv_addr.u.ss, + &sc->cli_addr.u.ss, + swn_dcesrv_connection_release_ip, + sc); + if (ret != 0) { + TALLOC_FREE(sc); + return NT_STATUS_INTERNAL_ERROR; + } + + return NT_STATUS_OK; +} + +#define DCESRV_INTERFACE_WITNESS_BIND(context, iface) \ + dcesrv_interface_witness_bind(context, iface) +static NTSTATUS dcesrv_interface_witness_bind(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface) +{ + NTSTATUS status; + + status = dcesrv_interface_witness_register_ips(context->conn); + if (!NT_STATUS_IS_OK(status)) { + /* + * This is not really critical, so we just print + * as warning... + */ + DBG_WARNING("dcesrv_interface_witness_register_ips() failed: %s\n", + nt_errstr(status)); + } + + /* + * [MS-SWN] Section 7. If the authentication level is not + * integrity or privacy level, Windows servers will fail the call + * with access denied + */ + return dcesrv_interface_bind_require_integrity(context, iface); +} + /**************************************************************** _witness_GetInterfaceList ****************************************************************/ @@ -29,8 +848,204 @@ WERROR _witness_GetInterfaceList(struct pipes_struct *p, struct witness_GetInterfaceList *r) { - p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; - return WERR_NOT_SUPPORTED; + struct dcesrv_context *dce_ctx = p->dce_call->conn->dce_ctx; + struct swn_service_interface *iface = NULL; + struct witness_interfaceList *list = NULL; + size_t num_interfaces = 0; + NTSTATUS status; + + status = swn_service_reload_interfaces(dce_ctx); + if (!NT_STATUS_IS_OK(status)) { + return ntstatus_to_werror(status); + } + + for (iface = swn_globals->interfaces.list; iface != NULL; iface = iface->next) { + num_interfaces += 1; + } + + list = talloc_zero(p->mem_ctx, struct witness_interfaceList); + if (list == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + list->interfaces = talloc_zero_array(list, + struct witness_interfaceInfo, + num_interfaces); + if (list->interfaces == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + for (iface = swn_globals->interfaces.list; iface != NULL; iface = iface->next) { + struct witness_interfaceInfo *info = + &list->interfaces[list->num_interfaces++]; + char addr[INET6_ADDRSTRLEN] = { 0, }; + const char *ipv4 = "0.0.0.0"; + const char *ipv6 = "::"; + uint32_t flags = 0; + + print_sockaddr(addr, sizeof(addr), &iface->addr.u.ss); + if (iface->addr.u.sa.sa_family == AF_INET) { + flags |= WITNESS_INFO_IPv4_VALID; + ipv4 = addr; + } else if (iface->addr.u.sa.sa_family == AF_INET6) { + flags |= WITNESS_INFO_IPv6_VALID; + ipv6 = addr; + } + + if (!iface->local_iface) { + /* + * If it's not a local interface + * it is able to serve as + * witness server + */ + flags |= WITNESS_INFO_WITNESS_IF; + } + + info->group_name = talloc_strdup(list, iface->group_name); + if (info->group_name == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + info->version = WITNESS_V2; /* WitnessServiceVersion; */ + info->state = iface->state; + info->ipv4 = talloc_strdup(list, ipv4); + if (info->ipv4 == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + info->ipv6 = talloc_strdup(list, ipv6); + if (info->ipv6 == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + info->flags = flags; + } + + *r->out.interface_list = list; + return WERR_OK; +} + +static WERROR swn_server_registration_create(struct swn_service_globals *swn, + struct pipes_struct *p, + const struct witness_RegisterEx *r, + const struct swn_service_interface *iface, + struct swn_service_registration **preg) +{ + struct swn_service_registration *reg = NULL; + + /* + * [MS-SWN] 3.1.4.5 + * The server MUST create a WitnessRegistration entry as follows and + * insert it into the WitnessRegistrationList. + */ + reg = talloc_zero(p->mem_ctx, struct swn_service_registration); + if (reg == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + reg->swn = swn; + + reg->client.version = r->in.version; + + /* + * all other string values are checked against + * well known expected values. + * + * So we better escape the client_computer_name + * if it contains strange things... + */ + reg->client.computer_name = log_escape(reg, r->in.client_computer_name); + if (reg->client.computer_name == NULL) { + TALLOC_FREE(reg); + return WERR_NOT_ENOUGH_MEMORY; + } + + reg->net_name = talloc_strdup(reg, r->in.net_name); + if (reg->net_name == NULL) { + TALLOC_FREE(reg); + return WERR_NOT_ENOUGH_MEMORY; + } + + reg->ip_address = iface->addr; + + if (r->in.share_name != NULL) { + reg->share_name = talloc_strdup(reg, r->in.share_name); + if (reg->share_name == NULL) { + TALLOC_FREE(reg); + return WERR_NOT_ENOUGH_MEMORY; + } + } + + reg->async_notify.timeout_secs = r->in.timeout; + reg->async_notify.queue = tevent_queue_create(reg, "async_notify"); + if (reg->async_notify.queue == NULL) { + TALLOC_FREE(reg); + return WERR_NOT_ENOUGH_MEMORY; + } + tevent_queue_stop(reg->async_notify.queue); + + reg->usage.create_time = p->dce_call->time; + reg->usage.unused_timeout_secs = + swn_globals->registrations.unused_timeout_secs; + /* + * swn_service_registration_update_usage() below + * will update the timer to its real expire time! + */ + reg->usage.expire_time = (struct timeval) { .tv_sec = TIME_T_MAX, }; + reg->usage.timer = tevent_add_timer(p->dce_call->event_ctx, + reg, + reg->usage.expire_time, + swn_service_registration_unused, + reg); + if (reg->usage.timer == NULL) { + TALLOC_FREE(reg); + return WERR_NOT_ENOUGH_MEMORY; + } + swn_service_registration_update_usage(reg, reg->usage.create_time); + + reg->key.ptr = create_policy_hnd(p, ®->key.handle, + SWN_SERVICE_CONTEXT_HANDLE_REGISTRATION, + reg); + if (reg->key.ptr == NULL) { + TALLOC_FREE(reg); + return WERR_NO_SYSTEM_RESOURCES; + } + + DLIST_ADD_END(swn_globals->registrations.list, reg); + talloc_set_destructor(reg, swn_service_registration_destructor); + + *preg = reg; + return WERR_OK; +} + +static WERROR swn_server_check_net_name(struct swn_service_globals *swn, + const char *net_name) +{ + TALLOC_CTX *frame = talloc_stackframe(); + char *stripped_net_name = NULL; + char *p = NULL; + bool ok; + + ok = strequal(swn->server_global_name, net_name); + if (ok) { + TALLOC_FREE(frame); + return WERR_OK; + } + + stripped_net_name = talloc_strdup(frame, net_name); + if (stripped_net_name == NULL) { + TALLOC_FREE(frame); + return WERR_NOT_ENOUGH_MEMORY; + } + + p = strchr(stripped_net_name, '.'); + if (p != NULL) { + *p = '\0'; + } + + ok = is_myname(stripped_net_name); + if (ok) { + TALLOC_FREE(frame); + return WERR_OK; + } + + TALLOC_FREE(frame); + return WERR_INVALID_PARAMETER; } /**************************************************************** @@ -40,8 +1055,124 @@ WERROR _witness_GetInterfaceList(struct pipes_struct *p, WERROR _witness_Register(struct pipes_struct *p, struct witness_Register *r) { - p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; - return WERR_NOT_SUPPORTED; + struct dcesrv_context *dce_ctx = p->dce_call->conn->dce_ctx; + struct swn_service_registration *reg = NULL; + struct samba_sockaddr addr = { .sa_socklen = 0, }; + struct swn_service_interface *iface = NULL; + const struct witness_RegisterEx rex = { + .in = { + .version = r->in.version, + .net_name = r->in.net_name, + .ip_address = r->in.ip_address, + .client_computer_name = r->in.client_computer_name, + }, + }; + NTSTATUS status; + WERROR werr; + bool ok; + + /* + * [MS-SWN] 3.1.4.2 + * If the Version field of the request is not 0x00010001, the server + * MUST stop processing the request and return the error code + * ERROR_REVISION_MISMATCH + */ + if (r->in.version != WITNESS_V1) { + return WERR_REVISION_MISMATCH; + } + + /* + * [MS-SWN] 3.1.4.2 + * If NetName, IpAddress or ClientComputerName is NULL, the server + * MUST fail the request and return the error code + * ERROR_INVALID_PARAMETER + */ + if (r->in.net_name == NULL || + r->in.ip_address == NULL || + r->in.client_computer_name == NULL) + { + return WERR_INVALID_PARAMETER; + } + + status = swn_service_reload_interfaces(dce_ctx); + if (!NT_STATUS_IS_OK(status)) { + return ntstatus_to_werror(status); + } + + /* + * [MS-SWN] 3.1.4.2 + * If the NetName parameter is not equal to ServerGlobalName, the + * server MUST fail the request and return the error code + * ERROR_INVALID_PARAMETER + */ + werr = swn_server_check_net_name(swn_globals, r->in.net_name); + if (!W_ERROR_IS_OK(werr)) { + DBG_INFO("Invalid net_name[%s], " + "server_global_name[%s]: %s\n", + log_escape(p->mem_ctx, r->in.net_name), + swn_globals->server_global_name, + win_errstr(werr)); + return werr; + } + + /* + * [MS-SWN] 3.1.4.2 + * The server MUST enumerate the shares by calling NetrShareEnum as + * specified in [MS-SRVS] section 3.1.4.8. In the enumerated list, + * if any of the shares has shi*_type set to STYPE_CLUSTER_SOFS, as + * specified in [MS-SRVS] section 2.2.2.4, the server MUST search for + * an Interface in InterfaceList, where Interface.IPv4Address or + * Interface.IPv6Address matches the IpAddress parameter based on its + * format. If no matching entry is found, the server MUST fail the + * request and return the error code ERROR_INVALID_STATE. + * + * After clarifying this point with dochelp: + * A server only sets the CLUSTER_SOFS, CLUSTER_FS, or CLUSTER_DFS bit + * flags in NetrShareEnum when the call is local and never will be set + * by remote calls. This point only serves the purpose of identifying + * the SOFS shares. + * The server returns the error code ERROR_INVALID_STATE if the share + * enumeration of SMB share resources fails with any error other than + * STATUS_SUCCESS. It’s not the absence of SOFS shares, or just the + * call to ShareEnum. When the server enumerates the shares by calling + * NetrShareEnum locally, it tries to filter out only shares with + * STYPE_CLUSTER_SOFS. The scope of 'If no matching entry is found' + * is broader. Even if shares have STYPE_CLUSTER_SOFS, but no match + * could be found with the IpAddress, ERROR_INVALID_STATE will be + * returned too. + * + * In a CTDB cluster, all shares in the clustered filesystem are + * scale-out. We can skip this check and proceed to find the matching + * IP address. + */ + ok = is_ipaddress(r->in.ip_address); + if (!ok) { + DBG_INFO("Invalid ip_address[%s]\n", + log_escape(p->mem_ctx, r->in.ip_address)); + return WERR_INVALID_STATE; + } + ok = interpret_string_addr(&addr.u.ss, + r->in.ip_address, + AI_PASSIVE|AI_NUMERICHOST); + if (!ok) { + DBG_INFO("Invalid ip_address[%s]\n", + log_escape(p->mem_ctx, r->in.ip_address)); + return WERR_INVALID_STATE; + } + iface = swn_service_interface_by_addr(swn_globals, &addr); + if (iface == NULL) { + DBG_INFO("Invalid ip_address[%s]\n", + log_escape(p->mem_ctx, r->in.ip_address)); + return WERR_INVALID_STATE; + } + + werr = swn_server_registration_create(swn_globals, p, &rex, iface, ®); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + *r->out.context_handle = reg->key.handle; + return WERR_OK; } @@ -52,19 +1183,421 @@ WERROR _witness_Register(struct pipes_struct *p, WERROR _witness_UnRegister(struct pipes_struct *p, struct witness_UnRegister *r) { - p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; - return WERR_NOT_SUPPORTED; + bool ok; + + /* + * [MS-SWN] 3.1.4.3 + * The server MUST search for the WitnessRegistration in + * WitnessRegistrationList, where WitnessRegistration.RegistrationKey + * matches the pContext parameter. If no matching entry is found, + * the server SHOULD<4> stop processing the request and return the + * error code ERROR_NOT_FOUND. + */ + ok = close_policy_hnd(p, &r->in.context_handle); + if (!ok) { + if (p->fault_state != 0) { + p->fault_state = 0; + } + return WERR_NOT_FOUND; + } + + return WERR_OK; } /**************************************************************** _witness_AsyncNotify ****************************************************************/ +struct swn_service_async_notify_state { + struct swn_service_async_notify_state *prev, *next; + struct tevent_context *ev; + struct tevent_req *req; + TALLOC_CTX *r_mem_ctx; + struct witness_AsyncNotify *r; + struct swn_service_registration *reg; + struct tevent_queue_entry *qe; +}; + +static void swn_service_async_notify_trigger(struct tevent_req *req, + void *private_data); + +static void swn_service_async_notify_cleanup(struct tevent_req *req, + enum tevent_req_state req_state) +{ + struct swn_service_async_notify_state *state = + tevent_req_data(req, + struct swn_service_async_notify_state); + + TALLOC_FREE(state->qe); + + if (state->reg != NULL) { + DLIST_REMOVE(state->reg->async_notify.list, state); + state->reg = NULL; + } +} + +static void swn_service_async_notify_reg_destroyed(struct swn_service_async_notify_state *state) +{ + swn_service_async_notify_cleanup(state->req, TEVENT_REQ_USER_ERROR); + swn_service_async_notify_trigger(state->req, NULL); +} + +static bool swn_service_async_notify_cancel(struct tevent_req *req) +{ + return false; +} + +static struct tevent_req *swn_service_async_notify_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + TALLOC_CTX *r_mem_ctx, + struct witness_AsyncNotify *r, + struct swn_service_registration *reg) +{ + struct tevent_req *req = NULL; + struct swn_service_async_notify_state *state = NULL; + struct timeval now = timeval_current(); + + req = tevent_req_create(mem_ctx, &state, + struct swn_service_async_notify_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->req = req; + state->r_mem_ctx = r_mem_ctx; + state->r = r; + state->reg = reg; + + /* + * triggered changes likely wakeup + * more than one waiter, so we better + * let all individual waiters go through + * a tevent_immediate round. + */ + tevent_req_defer_callback(req, ev); + + tevent_req_set_cleanup_fn(req, swn_service_async_notify_cleanup); + tevent_req_set_cancel_fn(req, swn_service_async_notify_cancel); + + if (!reg->change_notification.triggered) { + tevent_queue_stop(reg->async_notify.queue); + } + + DLIST_ADD_END(reg->async_notify.list, state); + + state->qe = tevent_queue_add_entry(reg->async_notify.queue, ev, req, + swn_service_async_notify_trigger, + NULL) + if (tevent_req_nomem(state->qe, req)) { + return tevent_req_post(req, ev); + } + + if (reg->async_notify.timeout_secs != 0) { + struct timeval endtime; + bool ok; + + endtime = timeval_add(&now, reg->async_notify.timeout_secs, 0); + ok = tevent_req_set_endtime(req, ev, endtime); + if (!ok) { + tevent_req_oom(req); + return tevent_req_post(req, ev); + } + } + + /* + * Once we added the queue entry + * swn_service_registration_update_usage() + * will adjust the registration expire time... + */ + swn_service_registration_update_usage(state->reg, now); + + /* + * Wait for trigger or timeout... + */ + return req; +} + +static void swn_service_async_notify_trigger(struct tevent_req *req, + void *private_data) +{ + struct swn_service_async_notify_state *state = + tevent_req_data(req, + struct swn_service_async_notify_state); + struct swn_service_registration *reg = state->reg; + struct witness_notifyResponse *resp = NULL; + WERROR forced_result = WERR_OK; + bool defer_forced_unregister = false; + + if (reg == NULL) { + tevent_req_werror(req, WERR_NOT_FOUND); + return; + } + + if (reg->change_notification.triggered) { + struct swn_service_globals *swn = reg->swn; + const struct swn_service_interface *iface = NULL; + union witness_notifyResponse_message *msgs = NULL; + char reg_ip[INET6_ADDRSTRLEN] = { 0, }; + struct witness_ResourceChange *rc = NULL; + enum witness_interfaceInfo_state cur_state; + + print_sockaddr(reg_ip, sizeof(reg_ip), ®->ip_address.u.ss); + + iface = swn_service_interface_by_addr(swn, ®->ip_address); + if (iface != NULL) { + cur_state = iface->state; + } else { + /* + * If the interface is no longer in our list + * it must be unavailable + */ + cur_state = WITNESS_STATE_UNAVAILABLE; + } + if (cur_state != WITNESS_STATE_AVAILABLE) { + reg->change_notification.last_ip_state = cur_state; + } + + resp = talloc_zero(state, struct witness_notifyResponse); + if (tevent_req_nomem(resp, req)) { + return; + } + + msgs = talloc_zero_array(resp, + union witness_notifyResponse_message, + 1); + if (tevent_req_nomem(msgs, req)) { + return; + } + + resp->type = WITNESS_NOTIFY_RESOURCE_CHANGE; + resp->num = 0; + resp->messages = msgs; + + rc = &msgs[resp->num].resource_change; + + switch (reg->change_notification.last_ip_state) { + case WITNESS_STATE_AVAILABLE: + rc->type = WITNESS_RESOURCE_STATE_AVAILABLE; + break; + case WITNESS_STATE_UNAVAILABLE: + rc->type = WITNESS_RESOURCE_STATE_UNAVAILABLE; + break; + case WITNESS_STATE_UNKNOWN: + rc->type = WITNESS_RESOURCE_STATE_UNKNOWN; + break; + } + + /* + * We use the ip address as resource name + */ + rc->name = talloc_strdup(msgs, reg_ip); + if (tevent_req_nomem(rc->name, req)) { + return; + } + + resp->num += 1; + + if (rc->type != WITNESS_RESOURCE_STATE_AVAILABLE) { + /* + * In order to let a Windows server 2022 + * correctly re-register after moving + * to a new connection, we force an + * unregistration after 5 seconds. + * + * It means the client gets WERR_NOT_FOUND + * from a pending AsyncNotify() and calls + * Unregister() (which also gets WERR_NOT_FOUND). + * Then the client calls GetInterfaceList() + * and RegisterEx() again. + */ + defer_forced_unregister = true; + } + + if (reg->change_notification.last_ip_state != cur_state) { + /* + * This means the last_ip_state was *not* available, + * and the current_state *is* available. + * + * keep the queue running and return the available + * message in the next run + */ + reg->change_notification.last_ip_state = cur_state; + goto finished; + } + + reg->change_notification.triggered = false; + reg->change_notification.last_ip_state = WITNESS_STATE_UNKNOWN; + goto finished; + } + +finished: + if (!reg->change_notification.triggered) { + tevent_queue_stop(reg->async_notify.queue); + } + + if (defer_forced_unregister) { + struct tevent_timer *te = NULL; + + /* + * In order to let a Windows server 2022 + * correctly re-register after moving + * to a new connection, we force an + * unregistration after 5 seconds. + * + * It means the client gets WERR_NOT_FOUND + * from a pending AsyncNotify() and calls + * Unregister() (which also gets WERR_NOT_FOUND). + * Then the client calls GetInterfaceList() + * and RegisterEx() again. + */ + TALLOC_FREE(reg->forced_unregister.timer); + te = tevent_add_timer(state->ev, + reg, + timeval_current_ofs(5,0), + swn_service_registration_force_unregister, + reg); + if (tevent_req_nomem(te, req)) { + return; + } + reg->forced_unregister.timer = te; + } + + *state->r->out.response = talloc_move(state->r_mem_ctx, &resp); + state->r->out.result = forced_result; + if (!W_ERROR_IS_OK(forced_result)) { + tevent_req_werror(req, forced_result); + return; + } + tevent_req_done(req); +} + +static WERROR swn_service_async_notify_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_werror(req); +} + +struct _witness_AsyncNotify_state { + struct dcesrv_call_state *dce_call; + struct witness_AsyncNotify *r; + struct swn_service_registration *reg; + struct tevent_req *subreq; +}; + +static bool _witness_AsyncNotify_cancel(struct tevent_req *req); +static void _witness_AsyncNotify_done(struct tevent_req *subreq); + WERROR _witness_AsyncNotify(struct pipes_struct *p, struct witness_AsyncNotify *r) { - p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; - return WERR_NOT_SUPPORTED; + struct tevent_req *req = NULL; + struct _witness_AsyncNotify_state *state = NULL; + struct swn_service_registration *reg = NULL; + NTSTATUS status = NT_STATUS_INTERNAL_ERROR; + + /* + * [MS-SWN] 3.1.4.4 + * The server MUST search for the WitnessRegistration in + * WitnessRegistrationList, where WitnessRegistration.RegistrationKey + * matches the pContext parameter. If no matching entry is found, the + * server MUST fail the request and return the error code + * ERROR_NOT_FOUND. + */ + reg = find_policy_by_hnd(p, &r->in.context_handle, + SWN_SERVICE_CONTEXT_HANDLE_REGISTRATION, + struct swn_service_registration, + &status); + if (!NT_STATUS_IS_OK(status)) { + if (p->fault_state != 0) { + p->fault_state = 0; + } + return WERR_NOT_FOUND; + } + + swn_service_registration_update_usage(reg, p->dce_call->time); + + req = tevent_req_create(p->mem_ctx, &state, + struct _witness_AsyncNotify_state); + if (req == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + state->dce_call = p->dce_call; + state->r = r; + state->reg = reg; + + tevent_req_set_cancel_fn(req, _witness_AsyncNotify_cancel); + + state->subreq = swn_service_async_notify_send(state, + state->dce_call->event_ctx, + state->dce_call, + state->r, + state->reg); + if (state->subreq == NULL) { + TALLOC_FREE(state); + return WERR_NOT_ENOUGH_MEMORY; + } + tevent_req_set_callback(state->subreq, + _witness_AsyncNotify_done, + req); + + state->dce_call->subreq = req; + state->dce_call->state_flags |= DCESRV_CALL_STATE_FLAG_ASYNC; + return WERR_EVENT_PENDING; /* hidden by DCESRV_CALL_STATE_FLAG_ASYNC */ +} + +static bool _witness_AsyncNotify_cancel(struct tevent_req *req) +{ + struct _witness_AsyncNotify_state *state = + tevent_req_data(req, + struct _witness_AsyncNotify_state); + struct dcesrv_call_state *dce_call = state->dce_call; + + SMB_ASSERT(dce_call->subreq == req); + dce_call->subreq = NULL; + + TALLOC_FREE(state->subreq); + + if (dce_call->got_orphaned) { + dce_call->fault_code = DCERPC_FAULT_SERVER_UNAVAILABLE; + } else { + dce_call->fault_code = DCERPC_NCA_S_FAULT_CANCEL; + } + state->r->out.result = WERR_RPC_S_CALL_CANCELLED; + + dcesrv_async_reply(dce_call); + return true; +} + +static void _witness_AsyncNotify_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct _witness_AsyncNotify_state *state = + tevent_req_data(req, + struct _witness_AsyncNotify_state); + struct dcesrv_call_state *dce_call = state->dce_call; + + SMB_ASSERT(dce_call->subreq == req); + dce_call->subreq = NULL; + + SMB_ASSERT(state->subreq == subreq); + state->subreq = NULL; + + state->r->out.result = swn_service_async_notify_recv(subreq); + TALLOC_FREE(subreq); + + if (W_ERROR_EQUAL(state->r->out.result, WERR_NOT_FOUND)) { + state->reg = NULL; + } + + if (state->reg != NULL && + tevent_queue_length(state->reg->async_notify.queue) == 0) + { + struct timeval now = timeval_current(); + swn_service_registration_update_usage(state->reg, now); + } + + dcesrv_async_reply(dce_call); } /**************************************************************** @@ -74,8 +1607,136 @@ WERROR _witness_AsyncNotify(struct pipes_struct *p, WERROR _witness_RegisterEx(struct pipes_struct *p, struct witness_RegisterEx *r) { - p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; - return WERR_NOT_SUPPORTED; + struct dcesrv_context *dce_ctx = p->dce_call->conn->dce_ctx; + struct swn_service_registration *reg = NULL; + struct samba_sockaddr addr = { .sa_socklen = 0, }; + struct swn_service_interface *iface = NULL; + NTSTATUS status; + WERROR werr; + bool ok; + + /* + * [MS-SWN] 3.1.4.5 + * If the Version field of the request is not 0x00020000, the server + * MUST stop processing the request and return the error code + * ERROR_REVISION_MISMATCH + */ + if (r->in.version != WITNESS_V2) { + return WERR_REVISION_MISMATCH; + } + + /* + * [MS-SWN] 3.1.4.5 + * If NetName, IpAddress or ClientComputerName is NULL, the server + * MUST fail the request and return the error code + * ERROR_INVALID_PARAMETER + */ + if (r->in.net_name == NULL || + r->in.ip_address == NULL || + r->in.client_computer_name == NULL) + { + return WERR_INVALID_PARAMETER; + } + + status = swn_service_reload_interfaces(dce_ctx); + if (!NT_STATUS_IS_OK(status)) { + return ntstatus_to_werror(status); + } + + /* + * [MS-SWN] 3.1.4.5 + * If the NetName parameter is not equal to ServerGlobalName, the + * server MUST fail the request and return the error code + * ERROR_INVALID_PARAMETER + */ + werr = swn_server_check_net_name(swn_globals, r->in.net_name); + if (!W_ERROR_IS_OK(werr)) { + DBG_INFO("Invalid net_name[%s], " + "server_global_name[%s]: %s\n", + log_escape(p->mem_ctx, r->in.net_name), + swn_globals->server_global_name, + win_errstr(werr)); + return werr; + } + + /* + * [MS-SWN] 3.1.4.5 + * If ShareName is not NULL, the server MUST enumerate the shares by + * calling NetrShareEnum as specified in [MS-SRVS] section 3.1.4.8. + * If the enumeration fails or if no shares are returned, the server + * MUST return the error code ERROR_INVALID_STATE. + * + * If none of the shares in the list has shi*_type set to + * STYPE_CLUSTER_SOFS as specified in [MS-SRVS] section 3.1.4.8, + * the server MUST ignore ShareName. + * + * In a CTDB cluster, all shares in the clustered filesystem are + * scale-out. Check if the provided share name is in a clustered FS + */ + if (r->in.share_name != NULL) { + char *save_share = NULL; + int cmp; + + /* + * For now we allow all shares... + * + * The main reason is that windows + * clients typically connect as + * machine account, so things like %U + * wouldn't work anyway. + * + * And in the end it's just a string, + * so we just check it's sane. + */ + save_share = log_escape(p->mem_ctx, r->in.share_name); + if (save_share == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + cmp = strcmp(save_share, r->in.share_name); + if (cmp != 0) { + DBG_INFO("Invalid share_name[%s]\n", + save_share); + return WERR_INVALID_STATE; + } + TALLOC_FREE(save_share); + } + + /* + * [MS-SWN] 3.1.4.5 + * The server MUST search for an Interface in InterfaceList, where + * Interface.IPv4Address or Interface.IPv6Address matches the + * IpAddress parameter based on its format. If no matching entry is + * found, the server MUST fail the request and return the error code + * ERROR_INVALID_STATE. + */ + ok = is_ipaddress(r->in.ip_address); + if (!ok) { + DBG_INFO("Invalid ip_address[%s]\n", + log_escape(p->mem_ctx, r->in.ip_address)); + return WERR_INVALID_STATE; + } + ok = interpret_string_addr(&addr.u.ss, + r->in.ip_address, + AI_PASSIVE|AI_NUMERICHOST); + if (!ok) { + DBG_INFO("Invalid ip_address[%s]\n", + log_escape(p->mem_ctx, r->in.ip_address)); + return WERR_INVALID_STATE; + } + iface = swn_service_interface_by_addr(swn_globals, &addr); + if (iface == NULL) { + DBG_INFO("Invalid ip_address[%s]\n", + log_escape(p->mem_ctx, r->in.ip_address)); + return WERR_INVALID_STATE; + } + + werr = swn_server_registration_create(swn_globals, p, r, iface, ®); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + *r->out.context_handle = reg->key.handle; + return WERR_OK; } /* include the generated boilerplate */ -- 2.11.4.GIT