diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
commit | 8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch) | |
tree | 4099e8021376c7d8c05bdf8503093d80e9c7bad0 /source3/rpc_server/witness | |
parent | Initial commit. (diff) | |
download | samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip |
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source3/rpc_server/witness')
-rw-r--r-- | source3/rpc_server/witness/srv_witness_nt.c | 2465 |
1 files changed, 2465 insertions, 0 deletions
diff --git a/source3/rpc_server/witness/srv_witness_nt.c b/source3/rpc_server/witness/srv_witness_nt.c new file mode 100644 index 0000000..1d5f720 --- /dev/null +++ b/source3/rpc_server/witness/srv_witness_nt.c @@ -0,0 +1,2465 @@ +/* + * Unix SMB/CIFS implementation. + * + * 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 + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#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 "librpc/gen_ndr/ndr_rpcd_witness.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; + struct db_context *db; + } 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 tevent_context *ev_ctx; + struct messaging_context *msg_ctx; + struct tevent_req *subreq; + } msg; + + 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; + struct witness_notifyResponse *response; + WERROR result; + } forced_response; + + 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 { + bool triggered; + uint32_t new_node; + struct samba_sockaddr new_ip; + } move_notification; + + struct { + bool required; + bool triggered; + uint32_t new_node; + struct samba_sockaddr new_ip; + } share_notification; + + struct { + bool required; + bool triggered; + /* + * TODO: find how this works on windows and implement + * Windows Server 2022 as client doesn't use it... + */ + } ip_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; + struct GUID_txt_buf key_buf; + const char *key_str = GUID_buf_string(®->key.handle.uuid, &key_buf); + DATA_BLOB key_blob = data_blob_string_const(key_str); + TDB_DATA key = make_tdb_data(key_blob.data, key_blob.length); + NTSTATUS status; + + tevent_queue_stop(reg->async_notify.queue); + while (reg->async_notify.list != NULL) { + swn_service_async_notify_reg_destroyed(reg->async_notify.list); + } + + status = dbwrap_delete(reg->swn->registrations.db, key); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("rpcd_witness_registration: key '%s' delete - %s\n", + tdb_data_dbg(key), + nt_errstr(status)); + } else if (DEBUGLVL(DBGLVL_DEBUG)) { + DBG_DEBUG("rpcd_witness_registration: key '%s' deleted\n", + tdb_data_dbg(key)); + } + + 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; + char *global_path = 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; + } + + /* + * This contains secret information like client keys! + */ + global_path = lock_path(swn, "rpcd_witness_registration.tdb"); + if (global_path == NULL) { + TALLOC_FREE(swn); + return NT_STATUS_NO_MEMORY; + } + + swn->registrations.db = db_open(swn, global_path, + 0, /* hash_size */ + TDB_DEFAULT | + TDB_CLEAR_IF_FIRST | + TDB_INCOMPATIBLE_HASH, + O_RDWR | O_CREAT, 0600, + DBWRAP_LOCK_ORDER_1, + DBWRAP_FLAG_NONE); + if (swn->registrations.db == NULL) { + NTSTATUS status; + + status = map_nt_error_from_unix_common(errno); + TALLOC_FREE(swn); + + return status; + } + TALLOC_FREE(global_path); + + 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 +****************************************************************/ + +WERROR _witness_GetInterfaceList(struct pipes_struct *p, + struct witness_GetInterfaceList *r) +{ + 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 bool swn_server_registration_message_filter(struct messaging_rec *rec, void *private_data) +{ + struct swn_service_registration *reg = NULL; + struct policy_handle context_handle; + enum ndr_err_code ndr_err; + DATA_BLOB blob; + bool match; + + if (rec->msg_type != MSG_RPCD_WITNESS_REGISTRATION_UPDATE) { + return false; + } + + if (rec->num_fds != 0) { + return false; + } + + if (rec->buf.length < 20) { + return false; + } + + reg = talloc_get_type_abort(private_data, struct swn_service_registration); + + blob = data_blob_const(rec->buf.data, 20); + ndr_err = ndr_pull_struct_blob_all_noalloc(&blob, &context_handle, + (ndr_pull_flags_fn_t)ndr_pull_policy_handle); + SMB_ASSERT(NDR_ERR_CODE_IS_SUCCESS(ndr_err)); + + match = ndr_policy_handle_equal(&context_handle, ®->key.handle); + if (!match) { + return false; + } + + return true; +} + +static void swn_server_registration_client_move_to_node( + struct swn_service_registration *reg, + struct rpcd_witness_registration_update_move_to_node *move) +{ + reg->move_notification.triggered = true; + reg->move_notification.new_node = move->new_node; + reg->move_notification.new_ip = (struct samba_sockaddr) { + .sa_socklen = 0, + }; + + tevent_queue_start(reg->async_notify.queue); +} + +static void swn_server_registration_client_move_to_ip( + struct swn_service_registration *reg, + const char *new_ip_str) +{ + struct samba_sockaddr new_ip = { + .sa_socklen = 0, + }; + bool ok; + + ok = is_ipaddress(new_ip_str); + if (!ok) { + return; + } + ok = interpret_string_addr(&new_ip.u.ss, + new_ip_str, + AI_PASSIVE|AI_NUMERICHOST); + if (!ok) { + return; + } + + reg->move_notification.triggered = true; + reg->move_notification.new_node = NONCLUSTER_VNN; + reg->move_notification.new_ip = new_ip; + + tevent_queue_start(reg->async_notify.queue); +} + +static void swn_server_registration_client_move_to_ipv4( + struct swn_service_registration *reg, + struct rpcd_witness_registration_update_move_to_ipv4 *move) +{ + swn_server_registration_client_move_to_ip(reg, move->new_ipv4); +} + +static void swn_server_registration_client_move_to_ipv6( + struct swn_service_registration *reg, + struct rpcd_witness_registration_update_move_to_ipv6 *move) +{ + swn_server_registration_client_move_to_ip(reg, move->new_ipv6); +} + +static void swn_server_registration_share_move_to_node( + struct swn_service_registration *reg, + struct rpcd_witness_registration_update_move_to_node *move) +{ + if (!reg->share_notification.required) { + return; + } + + reg->share_notification.triggered = true; + reg->share_notification.new_node = move->new_node; + reg->share_notification.new_ip = (struct samba_sockaddr) { + .sa_socklen = 0, + }; + + tevent_queue_start(reg->async_notify.queue); +} + +static void swn_server_registration_share_move_to_ip( + struct swn_service_registration *reg, + const char *new_ip_str) +{ + struct samba_sockaddr new_ip = { + .sa_socklen = 0, + }; + bool ok; + + ok = is_ipaddress(new_ip_str); + if (!ok) { + return; + } + ok = interpret_string_addr(&new_ip.u.ss, + new_ip_str, + AI_PASSIVE|AI_NUMERICHOST); + if (!ok) { + return; + } + + if (!reg->share_notification.required) { + return; + } + + reg->share_notification.triggered = true; + reg->share_notification.new_node = NONCLUSTER_VNN; + reg->share_notification.new_ip = new_ip; + + tevent_queue_start(reg->async_notify.queue); +} + +static void swn_server_registration_share_move_to_ipv4( + struct swn_service_registration *reg, + struct rpcd_witness_registration_update_move_to_ipv4 *move) +{ + swn_server_registration_share_move_to_ip(reg, move->new_ipv4); +} + +static void swn_server_registration_share_move_to_ipv6( + struct swn_service_registration *reg, + struct rpcd_witness_registration_update_move_to_ipv6 *move) +{ + swn_server_registration_share_move_to_ip(reg, move->new_ipv6); +} + +static void swn_server_registration_force_response( + struct swn_service_registration *reg, + struct rpcd_witness_registration_update_force_response *response) +{ + reg->forced_response.triggered = true; + reg->forced_response.response = talloc_move(reg, &response->response); + reg->forced_response.result = response->result; + + tevent_queue_start(reg->async_notify.queue); +} + +static void swn_server_registration_message_done(struct tevent_req *subreq) +{ + struct swn_service_registration *reg = + tevent_req_callback_data(subreq, + struct swn_service_registration); + TALLOC_CTX *frame = talloc_stackframe(); + struct messaging_rec *rec = NULL; + struct rpcd_witness_registration_updateB update_blob; + enum ndr_err_code ndr_err; + NTSTATUS status; + int ret; + + SMB_ASSERT(reg->msg.subreq == subreq); + reg->msg.subreq = NULL; + + ret = messaging_filtered_read_recv(subreq, frame, &rec); + TALLOC_FREE(subreq); + if (ret != 0) { + status = map_nt_error_from_unix_common(ret); + DBG_ERR("messaging_filtered_read_recv() - %s\n", + nt_errstr(status)); + goto wait_for_next; + } + + DBG_DEBUG("MSG_RPCD_WITNESS_REGISTRATION_UPDATE: received...\n"); + + ndr_err = ndr_pull_struct_blob(&rec->buf, frame, &update_blob, + (ndr_pull_flags_fn_t)ndr_pull_rpcd_witness_registration_updateB); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DBG_ERR("ndr_pull_struct_blob - %s\n", nt_errstr(status)); + goto wait_for_next; + } + + if (DEBUGLVL(DBGLVL_DEBUG)) { + NDR_PRINT_DEBUG(rpcd_witness_registration_updateB, &update_blob); + } + + switch (update_blob.type) { + case RPCD_WITNESS_REGISTRATION_UPDATE_CLIENT_MOVE_TO_NODE: + swn_server_registration_client_move_to_node(reg, + &update_blob.update.client_move_to_node); + break; + case RPCD_WITNESS_REGISTRATION_UPDATE_CLIENT_MOVE_TO_IPV4: + swn_server_registration_client_move_to_ipv4(reg, + &update_blob.update.client_move_to_ipv4); + break; + case RPCD_WITNESS_REGISTRATION_UPDATE_CLIENT_MOVE_TO_IPV6: + swn_server_registration_client_move_to_ipv6(reg, + &update_blob.update.client_move_to_ipv6); + break; + case RPCD_WITNESS_REGISTRATION_UPDATE_SHARE_MOVE_TO_NODE: + swn_server_registration_share_move_to_node(reg, + &update_blob.update.share_move_to_node); + break; + case RPCD_WITNESS_REGISTRATION_UPDATE_SHARE_MOVE_TO_IPV4: + swn_server_registration_share_move_to_ipv4(reg, + &update_blob.update.share_move_to_ipv4); + break; + case RPCD_WITNESS_REGISTRATION_UPDATE_SHARE_MOVE_TO_IPV6: + swn_server_registration_share_move_to_ipv6(reg, + &update_blob.update.share_move_to_ipv6); + break; + case RPCD_WITNESS_REGISTRATION_UPDATE_FORCE_UNREGISTER: + TALLOC_FREE(reg); + TALLOC_FREE(frame); + return; + case RPCD_WITNESS_REGISTRATION_UPDATE_FORCE_RESPONSE: + swn_server_registration_force_response(reg, + &update_blob.update.force_response); + break; + } + +wait_for_next: + TALLOC_FREE(frame); + reg->msg.subreq = messaging_filtered_read_send(reg, + reg->msg.ev_ctx, + reg->msg.msg_ctx, + swn_server_registration_message_filter, + reg); + if (reg->msg.subreq == NULL) { + DBG_ERR("messaging_filtered_read_send() failed\n"); + return; + } + tevent_req_set_callback(reg->msg.subreq, + swn_server_registration_message_done, + reg); +} + +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; + const struct tsocket_address *client_address = + dcesrv_connection_get_remote_address(p->dce_call->conn); + const struct tsocket_address *server_address = + dcesrv_connection_get_local_address(p->dce_call->conn); + struct auth_session_info *session_info = + dcesrv_call_session_info(p->dce_call); + struct rpcd_witness_registration rg = { .version = 0, }; + enum ndr_err_code ndr_err; + NTSTATUS status; + struct GUID_txt_buf key_buf = {}; + const char *key_str = NULL; + DATA_BLOB key_blob = { .length = 0, }; + TDB_DATA key = { .dsize = 0, }; + DATA_BLOB val_blob = { .length = 0, }; + TDB_DATA val = { .dsize = 0, }; + + /* + * [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->share_notification.required = true; + } + + 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->ip_notification.required = (r->in.flags & + WITNESS_REGISTER_IP_NOTIFICATION); + + 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->msg.ev_ctx = p->dce_call->event_ctx; + reg->msg.msg_ctx = p->msg_ctx; + reg->msg.subreq = messaging_filtered_read_send(reg, + reg->msg.ev_ctx, + reg->msg.msg_ctx, + swn_server_registration_message_filter, + reg); + if (reg->msg.subreq == NULL) { + TALLOC_FREE(reg); + return WERR_NOT_ENOUGH_MEMORY; + } + tevent_req_set_callback(reg->msg.subreq, + swn_server_registration_message_done, + reg); + + 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); + + key_str = GUID_buf_string(®->key.handle.uuid, &key_buf); + key_blob = data_blob_string_const(key_str); + key = make_tdb_data(key_blob.data, key_blob.length); + + rg = (struct rpcd_witness_registration) { + .version = r->in.version, + .net_name = r->in.net_name, + .share_name = r->in.share_name, + .ip_address = r->in.ip_address, + .client_computer_name = reg->client.computer_name, + .flags = r->in.flags, + .timeout = r->in.timeout, + .context_handle = reg->key.handle, + .server_id = messaging_server_id(p->msg_ctx), + .account_name = session_info->info->account_name, + .domain_name = session_info->info->domain_name, + .account_sid = session_info->security_token->sids[PRIMARY_USER_SID_INDEX], + .local_address = tsocket_address_string(server_address, p->mem_ctx), + .remote_address = tsocket_address_string(client_address, p->mem_ctx), + .registration_time = timeval_to_nttime(&p->dce_call->time), + }; + + ndr_err = ndr_push_struct_blob(&val_blob, p->mem_ctx, &rg, + (ndr_push_flags_fn_t)ndr_push_rpcd_witness_registration); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DBG_WARNING("rpcd_witness_registration: key '%s' ndr_push - %s\n", + tdb_data_dbg(key), + nt_errstr(status)); + TALLOC_FREE(reg); + return WERR_NO_SYSTEM_RESOURCES; + } + val = make_tdb_data(val_blob.data, val_blob.length); + + status = dbwrap_store(reg->swn->registrations.db, key, val, TDB_INSERT); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("rpcd_witness_registration: key '%s' store - %s\n", + tdb_data_dbg(key), + nt_errstr(status)); + TALLOC_FREE(reg); + return WERR_NO_SYSTEM_RESOURCES; + } + + if (DEBUGLVL(DBGLVL_DEBUG)) { + DBG_DEBUG("rpcd_witness_registration: key '%s' stored\n", + tdb_data_dbg(key)); + NDR_PRINT_DEBUG(rpcd_witness_registration, &rg); + } + + *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; +} + +/**************************************************************** + _witness_Register +****************************************************************/ + +WERROR _witness_Register(struct pipes_struct *p, + struct witness_Register *r) +{ + 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; +} + + +/**************************************************************** + _witness_UnRegister +****************************************************************/ + +WERROR _witness_UnRegister(struct pipes_struct *p, + struct witness_UnRegister *r) +{ + 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->forced_response.triggered && + !reg->change_notification.triggered && + !reg->move_notification.triggered && + !reg->share_notification.triggered && + !reg->ip_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->forced_response.triggered) { + resp = talloc_move(state, ®->forced_response.response); + forced_result = reg->forced_response.result; + + reg->forced_response.triggered = false; + reg->forced_response.result = WERR_OK; + goto finished; + } + + 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; + } + + if (reg->move_notification.triggered) { + struct swn_service_globals *swn = reg->swn; + struct swn_service_interface *iface = NULL; + union witness_notifyResponse_message *msgs = NULL; + struct witness_IPaddrInfoList *list = NULL; + uint32_t num_ips = 0; + const uint32_t *new_node = NULL; + const struct samba_sockaddr *new_ip = NULL; + + if (reg->move_notification.new_node != NONCLUSTER_VNN) { + new_node = ®->move_notification.new_node; + } + if (!is_zero_addr(®->move_notification.new_ip.u.ss)) { + new_ip = ®->move_notification.new_ip; + } + + for (iface = swn->interfaces.list; + iface != NULL; + iface = iface->next) + { + if (new_node != NULL && + iface->current_vnn != *new_node) + { + continue; + } + + if (new_ip != NULL && + !sockaddr_equal(&new_ip->u.sa, &iface->addr.u.sa)) + { + continue; + } + + num_ips += 1; + } + + if (num_ips == 0) { + goto no_moves; + } + + 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; + } + + list = &msgs[0].client_move; + list->addr = talloc_zero_array(msgs, + struct witness_IPaddrInfo, + num_ips); + if (tevent_req_nomem(list->addr, req)) { + return; + } + + for (iface = swn->interfaces.list; + iface != NULL; + iface = iface->next) + { + struct witness_IPaddrInfo *info = &list->addr[list->num]; + char addr[INET6_ADDRSTRLEN] = { 0, }; + const char *ipv4 = "0.0.0.0"; + const char *ipv6 = "::"; + uint32_t flags = 0; + bool is_reg_ip = false; + + if (new_node != NULL && + iface->current_vnn != *new_node) + { + continue; + } + + if (new_ip != NULL && + !sockaddr_equal(&new_ip->u.sa, &iface->addr.u.sa)) + { + continue; + } + + switch (iface->state) { + case WITNESS_STATE_AVAILABLE: + flags |= WITNESS_IPADDR_ONLINE; + break; + case WITNESS_STATE_UNAVAILABLE: + flags |= WITNESS_IPADDR_OFFLINE; + break; + case WITNESS_STATE_UNKNOWN: + /* We map unknown also to offline */ + flags |= WITNESS_IPADDR_OFFLINE; + break; + } + + print_sockaddr(addr, sizeof(addr), &iface->addr.u.ss); + if (iface->addr.u.sa.sa_family == AF_INET) { + flags |= WITNESS_IPADDR_V4; + ipv4 = addr; + } else if (iface->addr.u.sa.sa_family == AF_INET6) { + flags |= WITNESS_IPADDR_V6; + ipv6 = addr; + } + + info->ipv4 = talloc_strdup(list, ipv4); + if (tevent_req_nomem(info->ipv4, req)) { + return; + } + info->ipv6 = talloc_strdup(list, ipv6); + if (tevent_req_nomem(info->ipv6, req)) { + return; + } + info->flags = flags; + list->num += 1; + + is_reg_ip = sockaddr_equal(®->ip_address.u.sa, + &iface->addr.u.sa); + if (!is_reg_ip) { + /* + * 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; + } + } + + resp->type = WITNESS_NOTIFY_CLIENT_MOVE; + resp->num = talloc_array_length(msgs); + resp->messages = msgs; + +no_moves: + reg->move_notification.triggered = false; + if (resp != NULL) { + goto finished; + } + } + + if (reg->share_notification.triggered) { + struct swn_service_globals *swn = reg->swn; + struct swn_service_interface *iface = NULL; + union witness_notifyResponse_message *msgs = NULL; + struct witness_IPaddrInfoList *list = NULL; + uint32_t num_ips = 0; + const uint32_t *new_node = NULL; + const struct samba_sockaddr *new_ip = NULL; + + if (reg->share_notification.new_node != NONCLUSTER_VNN) { + new_node = ®->share_notification.new_node; + } + if (!is_zero_addr(®->share_notification.new_ip.u.ss)) { + new_ip = ®->share_notification.new_ip; + } + + for (iface = swn->interfaces.list; + iface != NULL; + iface = iface->next) + { + if (new_node != NULL && + iface->current_vnn != *new_node) + { + continue; + } + + if (new_ip != NULL && + !sockaddr_equal(&new_ip->u.sa, &iface->addr.u.sa)) + { + continue; + } + + num_ips += 1; + } + + if (num_ips == 0) { + goto no_share_moves; + } + + 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; + } + + list = &msgs[0].client_move; + list->addr = talloc_zero_array(msgs, + struct witness_IPaddrInfo, + num_ips); + if (tevent_req_nomem(list->addr, req)) { + return; + } + + for (iface = swn->interfaces.list; + iface != NULL; + iface = iface->next) + { + struct witness_IPaddrInfo *info = &list->addr[list->num]; + char addr[INET6_ADDRSTRLEN] = { 0, }; + const char *ipv4 = "0.0.0.0"; + const char *ipv6 = "::"; + uint32_t flags = 0; + bool is_reg_ip = false; + + if (new_node != NULL && + iface->current_vnn != *new_node) + { + continue; + } + + if (new_ip != NULL && + !sockaddr_equal(&new_ip->u.sa, &iface->addr.u.sa)) + { + continue; + } + + switch (iface->state) { + case WITNESS_STATE_AVAILABLE: + flags |= WITNESS_IPADDR_ONLINE; + break; + case WITNESS_STATE_UNAVAILABLE: + flags |= WITNESS_IPADDR_OFFLINE; + break; + case WITNESS_STATE_UNKNOWN: + /* We map unknown also to offline */ + flags |= WITNESS_IPADDR_OFFLINE; + break; + } + + print_sockaddr(addr, sizeof(addr), &iface->addr.u.ss); + if (iface->addr.u.sa.sa_family == AF_INET) { + flags |= WITNESS_IPADDR_V4; + ipv4 = addr; + } else if (iface->addr.u.sa.sa_family == AF_INET6) { + flags |= WITNESS_IPADDR_V6; + ipv6 = addr; + } + + info->ipv4 = talloc_strdup(list, ipv4); + if (tevent_req_nomem(info->ipv4, req)) { + return; + } + info->ipv6 = talloc_strdup(list, ipv6); + if (tevent_req_nomem(info->ipv6, req)) { + return; + } + info->flags = flags; + list->num += 1; + + is_reg_ip = sockaddr_equal(®->ip_address.u.sa, + &iface->addr.u.sa); + if (!is_reg_ip) { + /* + * 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; + } + } + + resp->type = WITNESS_NOTIFY_SHARE_MOVE; + resp->num = talloc_array_length(msgs); + resp->messages = msgs; + +no_share_moves: + reg->share_notification.triggered = false; + if (resp != NULL) { + goto finished; + } + } + +finished: + if (!reg->forced_response.triggered && + !reg->change_notification.triggered && + !reg->move_notification.triggered && + !reg->share_notification.triggered && + !reg->ip_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) +{ + 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); +} + +/**************************************************************** + _witness_RegisterEx +****************************************************************/ + +WERROR _witness_RegisterEx(struct pipes_struct *p, + struct witness_RegisterEx *r) +{ + 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 */ +#include "librpc/gen_ndr/ndr_witness_scompat.c" |