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/utils/net_witness.c | |
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/utils/net_witness.c')
-rw-r--r-- | source3/utils/net_witness.c | 2361 |
1 files changed, 2361 insertions, 0 deletions
diff --git a/source3/utils/net_witness.c b/source3/utils/net_witness.c new file mode 100644 index 0000000..accff5b --- /dev/null +++ b/source3/utils/net_witness.c @@ -0,0 +1,2361 @@ +/* + * Samba Unix/Linux client library + * net witness commands to manage smb witness registrations + * Copyright (C) 2023 Stefan Metzmacher + * + * 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 "utils/net.h" +#include "messages.h" +#include "serverid.h" +#include "lib/util/util_tdb.h" +#include "source3/include/util_tdb.h" +#include "libcli/security/dom_sid.h" +#include "lib/dbwrap/dbwrap.h" +#include "lib/dbwrap/dbwrap_rbt.h" +#include "lib/dbwrap/dbwrap_open.h" +#include "lib/param/param.h" +#include "librpc/gen_ndr/ndr_rpcd_witness.h" +#include <regex.h> + +struct json_object; + +#ifdef HAVE_JANSSON +#include <jansson.h> +#include "audit_logging.h" /* various JSON helpers */ +#endif /* HAVE_JANSSON */ + +#undef strcasecmp + +static struct db_context *net_witness_open_registration_db(void) +{ + static struct db_context *db; + char *global_path = NULL; + + if (db != NULL) { + return db; + } + + global_path = lock_path(talloc_tos(), "rpcd_witness_registration.tdb"); + if (global_path == NULL) { + return NULL; + } + + db = db_open(NULL, + global_path, + 0, /* hash_size */ + TDB_DEFAULT | + TDB_CLEAR_IF_FIRST | + TDB_INCOMPATIBLE_HASH, + O_RDONLY, + 0600, + DBWRAP_LOCK_ORDER_1, + DBWRAP_FLAG_NONE); + TALLOC_FREE(global_path); + if (db == NULL) { + return NULL; + } + + return db; +} + +struct net_witness_scan_registrations_action_state { + bool (*prepare_fn)(void *private_data); + bool (*match_fn)(void *private_data, const struct rpcd_witness_registration *rg); + NTSTATUS (*process_fn)(void *private_data, const struct rpcd_witness_registration *rg); + void *private_data; +}; + +struct net_witness_scan_registrations_regex { + regex_t regex; + bool valid; +}; + +struct net_witness_scan_registrations_state { + struct net_context *c; + struct net_witness_scan_registrations_regex net_name; + struct net_witness_scan_registrations_regex share_name; + struct net_witness_scan_registrations_regex ip_address; + struct net_witness_scan_registrations_regex client_computer; + struct json_object *message_json; +#ifdef HAVE_JANSSON + struct json_object filters_json; + struct json_object registrations_json; +#endif + const struct net_witness_scan_registrations_action_state *action; + NTSTATUS error; +}; + +static bool net_witness_scan_registrations_regex_init( + struct net_witness_scan_registrations_state *state, + struct net_witness_scan_registrations_regex *r, + const char *option, const char *value); +static bool net_witness_scan_registrations_regex_match( + struct net_witness_scan_registrations_regex *r, + const char *name, const char *value); +static void net_witness_scan_registrations_regex_free( + struct net_witness_scan_registrations_regex *r); + +static bool net_witness_scan_registrations_match( + struct net_witness_scan_registrations_state *state, + const struct rpcd_witness_registration *rg) +{ + if (state->net_name.valid) { + bool match; + + match = net_witness_scan_registrations_regex_match( + &state->net_name, + "net_name", + rg->net_name); + if (!match) { + return false; + } + } + + if (state->share_name.valid) { + bool match; + + match = net_witness_scan_registrations_regex_match( + &state->share_name, + "share_name", + rg->share_name); + if (!match) { + return false; + } + } + + if (state->ip_address.valid) { + bool match; + + match = net_witness_scan_registrations_regex_match( + &state->ip_address, + "ip_address", + rg->ip_address); + if (!match) { + return false; + } + } + + if (state->client_computer.valid) { + bool match; + + match = net_witness_scan_registrations_regex_match( + &state->client_computer, + "client_computer_name", + rg->client_computer_name); + if (!match) { + return false; + } + } + + return true; +} + +static bool net_witness_scan_registrations_regex_init( + struct net_witness_scan_registrations_state *state, + struct net_witness_scan_registrations_regex *r, + const char *option, const char *value) +{ +#ifdef HAVE_JANSSON + struct net_context *c = state->c; +#endif /* HAVE_JANSSON */ + int ret; + + r->valid = false; + + if (value == NULL) { + return true; + } + + ret = regcomp(&r->regex, value, REG_EXTENDED|REG_ICASE|REG_NOSUB); + if (ret != 0) { + fstring buf = { 0,}; + regerror(ret, &r->regex, buf, sizeof(buf)); + d_printf("regcomp(%s) failed for %s: " + "%d: %s\n", value, option, ret, buf); + return false; + } + +#ifdef HAVE_JANSSON + if (c->opt_json) { + ret = json_add_string(&state->filters_json, + option, + value); + if (ret != 0) { + return false; + } + } +#endif /* HAVE_JANSSON */ + + r->valid = true; + return true; +} + +static bool net_witness_scan_registrations_regex_match( + struct net_witness_scan_registrations_regex *r, + const char *name, const char *value) +{ + int ret; + + if (!r->valid) { + return false; + } + + if (value == NULL) { + /* + * without a share name, + * we match against an empty + * string. + */ + value = ""; + } + + ret = regexec(&r->regex, value, 0, NULL, 0); + if (ret == REG_NOMATCH) { + return false; + } + + return true; +} + +static void net_witness_scan_registrations_regex_free( + struct net_witness_scan_registrations_regex *r) +{ + if (r->valid) { + regfree(&r->regex); + r->valid = false; + } +} + +static bool net_witness_scan_registrations_init( + struct net_witness_scan_registrations_state *state) +{ + struct net_context *c = state->c; + bool ok; + + if (c->opt_json) { +#ifdef HAVE_JANSSON + state->filters_json = json_new_object(); + if (json_is_invalid(&state->filters_json)) { + return false; + } + + if (c->opt_witness_registration != NULL) { + int ret; + + ret = json_add_string(&state->filters_json, + "--witness-registration", + c->opt_witness_registration); + if (ret != 0) { + return false; + } + } + + if (c->opt_witness_apply_to_all != 0) { + int ret; + + ret = json_add_bool(&state->filters_json, + "--witness-apply-to-all", + c->opt_witness_apply_to_all != 0); + if (ret != 0) { + return false; + } + } + + state->registrations_json = json_new_object(); + if (json_is_invalid(&state->registrations_json)) { + return false; + } +#else /* not HAVE_JANSSON */ + d_fprintf(stderr, _("JSON support not available\n")); + return false; +#endif /* not HAVE_JANSSON */ + } + + ok = net_witness_scan_registrations_regex_init(state, + &state->net_name, + "--witness-net-name", + c->opt_witness_net_name); + if (!ok) { + return false; + } + + ok = net_witness_scan_registrations_regex_init(state, + &state->share_name, + "--witness-share-name", + c->opt_witness_share_name); + if (!ok) { + return false; + } + + ok = net_witness_scan_registrations_regex_init(state, + &state->ip_address, + "--witness-ip-address", + c->opt_witness_ip_address); + if (!ok) { + return false; + } + + ok = net_witness_scan_registrations_regex_init(state, + &state->client_computer, + "--witness-client-computer-name", + c->opt_witness_client_computer_name); + if (!ok) { + return false; + } + + ok = state->action->prepare_fn(state->action->private_data); + if (!ok) { + return false; + } + + if (!c->opt_json) { + d_printf("%-36s %-20s %-15s %-20s %s\n", + "Registration-UUID:", + "NetName", + "ShareName", + "IpAddress", + "ClientComputerName"); + d_printf("%-36s-%-20s-%-15s-%-20s-%s\n", + "------------------------------------", + "--------------------", + "------------------", + "--------------------", + "------------------"); + } + + return true; +} + +static bool net_witness_scan_registrations_finish( + struct net_witness_scan_registrations_state *state) +{ +#ifdef HAVE_JANSSON + struct net_context *c = state->c; + struct json_object root_json = json_empty_object; + TALLOC_CTX *frame = NULL; + const char *json_str = NULL; + int ret; + + if (!c->opt_json) { + return true; + } + + frame = talloc_stackframe(); + + root_json = json_new_object(); + if (json_is_invalid(&root_json)) { + TALLOC_FREE(frame); + return false; + } + + ret = json_add_object(&root_json, + "filters", + &state->filters_json); + if (ret != 0) { + json_free(&root_json); + TALLOC_FREE(frame); + return false; + } + state->filters_json = json_empty_object; + + if (state->message_json != NULL) { + ret = json_add_object(&root_json, + "message", + state->message_json); + if (ret != 0) { + json_free(&root_json); + TALLOC_FREE(frame); + return false; + } + *state->message_json = json_empty_object; + } + + ret = json_add_object(&root_json, + "registrations", + &state->registrations_json); + if (ret != 0) { + json_free(&root_json); + TALLOC_FREE(frame); + return false; + } + state->registrations_json = json_empty_object; + + json_str = json_to_string(frame, &root_json); + json_free(&root_json); + if (json_str == NULL) { + TALLOC_FREE(frame); + return false; + } + + d_printf("%s\n", json_str); + TALLOC_FREE(frame); + return true; +#else /* not HAVE_JANSSON */ + return true; +#endif /* not HAVE_JANSSON */ +} + +static void net_witness_scan_registrations_free( + struct net_witness_scan_registrations_state *state) +{ +#ifdef HAVE_JANSSON + if (!json_is_invalid(&state->filters_json)) { + json_free(&state->filters_json); + } + if (!json_is_invalid(&state->registrations_json)) { + json_free(&state->registrations_json); + } +#endif /* HAVE_JANSSON */ + + net_witness_scan_registrations_regex_free(&state->net_name); + net_witness_scan_registrations_regex_free(&state->share_name); + net_witness_scan_registrations_regex_free(&state->ip_address); + net_witness_scan_registrations_regex_free(&state->client_computer); +} + +#ifdef HAVE_JANSSON +static int dump_registration_json(struct json_object *registrations_json, + const char *key_str, + const struct rpcd_witness_registration *rg) +{ + struct json_object jsobj = json_empty_object; + struct json_object flags_json = json_empty_object; + struct json_object context_json = json_empty_object; + struct json_object serverid_json = json_empty_object; + struct json_object auth_json = json_empty_object; + struct json_object connection_json = json_empty_object; + struct timeval tv; + struct dom_sid_buf sid_buf; + int ret = 0; + + jsobj = json_new_object(); + if (json_is_invalid(&jsobj)) { + d_fprintf(stderr, _("error setting up JSON value\n")); + goto failure; + } + + ret = json_add_flags32(&jsobj, "version", rg->version); + if (ret != 0) { + goto failure; + } + + ret = json_add_string(&jsobj, "net_name", rg->net_name); + if (ret != 0) { + goto failure; + } + + ret = json_add_string(&jsobj, "share_name", rg->share_name); + if (ret != 0) { + goto failure; + } + + ret = json_add_string(&jsobj, "ip_address", rg->ip_address); + if (ret != 0) { + goto failure; + } + + ret = json_add_string(&jsobj, "client_computer_name", rg->client_computer_name); + if (ret != 0) { + goto failure; + } + + flags_json = json_new_object(); + if (json_is_invalid(&flags_json)) { + goto failure; + } + + ret = json_add_bool(&flags_json, "WITNESS_REGISTER_IP_NOTIFICATION", + (rg->flags & WITNESS_REGISTER_IP_NOTIFICATION) ? + true : false); + if (ret != 0) { + goto failure; + } + + ret = json_add_int(&flags_json, "int", rg->flags); + if (ret != 0) { + goto failure; + } + + ret = json_add_flags32(&flags_json, "hex", rg->flags); + if (ret != 0) { + goto failure; + } + + ret = json_add_object(&jsobj, "flags", &flags_json); + if (ret != 0) { + goto failure; + } + flags_json = json_empty_object; + + ret = json_add_int(&jsobj, "timeout", rg->timeout); + if (ret != 0) { + goto failure; + } + + context_json = json_new_object(); + if (json_is_invalid(&context_json)) { + goto failure; + } + + ret = json_add_int(&context_json, "handle_type", rg->context_handle.handle_type); + if (ret != 0) { + goto failure; + } + + ret = json_add_guid(&context_json, "uuid", &rg->context_handle.uuid); + if (ret != 0) { + goto failure; + } + + ret = json_add_object(&jsobj, "context_handle", &context_json); + if (ret != 0) { + goto failure; + } + context_json = json_empty_object; + + serverid_json = json_new_object(); + if (json_is_invalid(&serverid_json)) { + goto failure; + } + + ret = json_add_int(&serverid_json, "pid", rg->server_id.pid); + if (ret != 0) { + goto failure; + } + + ret = json_add_int(&serverid_json, "task_id", rg->server_id.task_id); + if (ret != 0) { + goto failure; + } + + ret = json_add_int(&serverid_json, "vnn", rg->server_id.vnn); + if (ret != 0) { + goto failure; + } + + ret = json_add_int(&serverid_json, "unique_id", rg->server_id.unique_id); + if (ret != 0) { + goto failure; + } + + ret = json_add_object(&jsobj, "server_id", &serverid_json); + if (ret != 0) { + goto failure; + } + serverid_json = json_empty_object; + + auth_json = json_new_object(); + if (json_is_invalid(&auth_json)) { + goto failure; + } + + ret = json_add_string(&auth_json, "account_name", rg->account_name); + if (ret != 0) { + goto failure; + } + + ret = json_add_string(&auth_json, "domain_name", rg->domain_name); + if (ret != 0) { + goto failure; + } + + ret = json_add_string(&auth_json, + "account_sid", + dom_sid_str_buf(&rg->account_sid, &sid_buf)); + if (ret != 0) { + goto failure; + } + + ret = json_add_object(&jsobj, "auth", &auth_json); + if (ret != 0) { + goto failure; + } + auth_json = json_empty_object; + + connection_json = json_new_object(); + if (json_is_invalid(&connection_json)) { + goto failure; + } + + ret = json_add_string(&connection_json, "local_address", rg->local_address); + if (ret != 0) { + goto failure; + } + + ret = json_add_string(&connection_json, "remote_address", rg->remote_address); + if (ret != 0) { + goto failure; + } + + ret = json_add_object(&jsobj, "connection", &connection_json); + if (ret != 0) { + goto failure; + } + connection_json = json_empty_object; + + nttime_to_timeval(&tv, rg->registration_time); + ret = json_add_time(&jsobj, "registration_time", tv); + if (ret != 0) { + goto failure; + } + + ret = json_add_object(registrations_json, key_str, &jsobj); + if (ret != 0) { + goto failure; + } + jsobj = json_empty_object; + +failure: + if (!json_is_invalid(&connection_json)) { + json_free(&connection_json); + } + if (!json_is_invalid(&auth_json)) { + json_free(&auth_json); + } + if (!json_is_invalid(&serverid_json)) { + json_free(&serverid_json); + } + if (!json_is_invalid(&context_json)) { + json_free(&context_json); + } + if (!json_is_invalid(&flags_json)) { + json_free(&flags_json); + } + if (!json_is_invalid(&jsobj)) { + json_free(&jsobj); + } + + return ret; +} +#endif /* HAVE_JANSSON */ + +static NTSTATUS net_witness_scan_registrations_dump_rg( + struct net_witness_scan_registrations_state *state, + const struct rpcd_witness_registration *rg) +{ + struct net_context *c = state->c; + struct GUID_txt_buf key_buf; + const char *key_str = GUID_buf_string(&rg->context_handle.uuid, &key_buf); + + if (c->opt_json) { +#ifdef HAVE_JANSSON + int ret; + + ret = dump_registration_json(&state->registrations_json, + key_str, + rg); + if (ret != 0) { + d_fprintf(stderr, "dump_registration_json(%s) failed\n", + key_str); + return NT_STATUS_INTERNAL_ERROR; + } +#endif /* HAVE_JANSSON */ + return NT_STATUS_OK; + } + + d_printf("%-36s %-20s %-15s %-20s %s\n", + key_str, + rg->net_name, + rg->share_name ? rg->share_name : "''", + rg->ip_address, + rg->client_computer_name); + + return NT_STATUS_OK; +} + +static void net_witness_scan_registrations_parser(TDB_DATA key, + TDB_DATA val, + void *private_data) +{ + struct net_witness_scan_registrations_state *state = + (struct net_witness_scan_registrations_state *)private_data; + DATA_BLOB val_blob = data_blob_const(val.dptr, val.dsize); + struct rpcd_witness_registration rg; + enum ndr_err_code ndr_err; + TALLOC_CTX *frame = NULL; + bool match = false; + + if (val_blob.length == 0) { + return; + } + + frame = talloc_stackframe(); + + ndr_err = ndr_pull_struct_blob(&val_blob, frame, &rg, + (ndr_pull_flags_fn_t)ndr_pull_rpcd_witness_registration); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DBG_WARNING("Invalid record in rpcd_witness_registration.tdb:" + "key '%s' ndr_pull_struct_blob - %s\n", + tdb_data_dbg(key), + ndr_errstr(ndr_err)); + state->error = ndr_map_error2ntstatus(ndr_err); + TALLOC_FREE(frame); + return; + } + + if (!serverid_exists(&rg.server_id)) { + TALLOC_FREE(frame); + return; + } + + if (CHECK_DEBUGLVL(DBGLVL_DEBUG)) { + NDR_PRINT_DEBUG(rpcd_witness_registration, &rg); + } + + match = net_witness_scan_registrations_match(state, &rg); + if (!NT_STATUS_IS_OK(state->error)) { + TALLOC_FREE(frame); + return; + } + if (!match) { + TALLOC_FREE(frame); + return; + } + + match = state->action->match_fn(state->action->private_data, &rg); + if (!match) { + TALLOC_FREE(frame); + return; + } + + state->error = state->action->process_fn(state->action->private_data, &rg); + if (NT_STATUS_IS_OK(state->error)) { + state->error = net_witness_scan_registrations_dump_rg(state, + &rg); + } + TALLOC_FREE(frame); +} + +static int net_witness_scan_registrations_traverse_cb(struct db_record *rec, void *private_data) +{ + struct net_witness_scan_registrations_state *state = + (struct net_witness_scan_registrations_state *)private_data; + TDB_DATA key = dbwrap_record_get_key(rec); + TDB_DATA val = dbwrap_record_get_value(rec); + + net_witness_scan_registrations_parser(key, val, private_data); + + if (!NT_STATUS_IS_OK(state->error)) { + return -1; + } + + return 0; +} + +static int net_witness_scan_registrations(struct net_context *c, + struct json_object *message_json, + const struct net_witness_scan_registrations_action_state *action) +{ + struct net_witness_scan_registrations_state state = { + .c = c, + .message_json = message_json, + .action = action, + }; + struct db_context *db = NULL; + NTSTATUS status; + bool ok; + + db = net_witness_open_registration_db(); + if (db == NULL) { + d_printf("net_witness_open_registration_db() failed\n"); + return -1; + } + + ok = net_witness_scan_registrations_init(&state); + if (!ok) { + d_printf("net_witness_scan_registrations_init() failed\n"); + return -1; + } + + if (c->opt_witness_registration != NULL) { + const char *key_str = c->opt_witness_registration; + DATA_BLOB key_blob = data_blob_string_const(key_str); + TDB_DATA key = make_tdb_data(key_blob.data, key_blob.length); + + status = dbwrap_parse_record(db, + key, + net_witness_scan_registrations_parser, + &state); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + status = NT_STATUS_OK; + } + if (!NT_STATUS_IS_OK(status)) { + d_printf("dbwrap_parse_record(%s) failed: %s\n", + key_str, nt_errstr(status)); + net_witness_scan_registrations_free(&state); + return -1; + } + if (!NT_STATUS_IS_OK(state.error)) { + d_printf("net_witness_scan_registrations_parser(%s) failed: %s\n", + key_str, nt_errstr(state.error)); + net_witness_scan_registrations_free(&state); + return -1; + } + } else { + status = dbwrap_traverse_read(db, + net_witness_scan_registrations_traverse_cb, + &state, + NULL); /* count */ + if (!NT_STATUS_IS_OK(status)) { + d_printf("dbwrap_traverse_read() failed\n"); + net_witness_scan_registrations_free(&state); + return -1; + } + if (!NT_STATUS_IS_OK(state.error)) { + d_printf("net_witness_scan_registrations_traverse_cb() failed: %s\n", + nt_errstr(state.error)); + net_witness_scan_registrations_free(&state); + return -1; + } + } + + ok = net_witness_scan_registrations_finish(&state); + if (!ok) { + d_printf("net_witness_scan_registrations_finish() failed\n"); + return -1; + } + + net_witness_scan_registrations_free(&state); + return 0; +} + +struct net_witness_list_state { + struct net_context *c; +}; + +static bool net_witness_list_prepare_fn(void *private_data) +{ + return true; +} + +static bool net_witness_list_match_fn(void *private_data, + const struct rpcd_witness_registration *rg) +{ + return true; +} + +static NTSTATUS net_witness_list_process_fn(void *private_data, + const struct rpcd_witness_registration *rg) +{ + return NT_STATUS_OK; +} + +static void net_witness_filter_usage(void) +{ + d_printf(" Note: Only supported with clustering=yes!\n\n"); + d_printf(" Machine readable output can be generated with " + "the following option:\n" + "\n" + " --json\n" + "\n"); + d_printf(" The selection of registrations can be limited by " + "the following options:\n" + "\n" + " --witness-registration=REGISTRATION_UUID\n" + " This does a direct lookup for REGISTRATION_UUID\n" + " instead of doing a database traversal.\n" + "\n" + " The following options all take a " + "POSIX Extended Regular Expression,\n" + " which can further filter the selection of " + "registrations.\n" + " These options are applied as logical AND, " + "but each REGEX \n" + " allows specifying multiple strings using " + "the pipe symbol.\n" + "\n" + " --witness-net-name=REGEX\n" + " This specifies the 'server name' the client\n" + " registered for monitoring.\n" + "\n" + " --witness-share-name=REGEX\n" + " This specifies the 'share name' the client\n" + " registered for monitoring.\n" + " Note that the share name is optional in the\n" + " registration, otherwise an empty string is \n" + " matched.\n" + "\n" + " --witness-ip-address=REGEX\n" + " This specifies the ip address the client\n" + " registered for monitoring.\n" + "\n" + " --witness-client-computer-name=REGEX\n" + " This specifies the client computer name the client\n" + " specified in the registration.\n" + " Note it is just a string chosen by the " + "client itself.\n" + "\n"); +} + +static void net_witness_list_usage(void) +{ + d_printf("%s\n" + "net witness list\n" + " %s\n\n", + _("Usage:"), + _("List witness registrations " + "from rpcd_witness_registration.tdb")); + net_witness_filter_usage(); +} + +static int net_witness_list(struct net_context *c, int argc, const char **argv) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct net_witness_list_state state = { .c = c, }; + struct net_witness_scan_registrations_action_state action = { + .prepare_fn = net_witness_list_prepare_fn, + .match_fn = net_witness_list_match_fn, + .process_fn = net_witness_list_process_fn, + .private_data = &state, + }; + int ret = -1; + + if (c->display_usage) { + net_witness_list_usage(); + goto out; + } + + if (argc != 0) { + net_witness_list_usage(); + goto out; + } + + if (!lp_clustering()) { + d_printf("ERROR: Only supported with clustering=yes!\n\n"); + goto out; + } + + ret = net_witness_scan_registrations(c, NULL, &action); + if (ret != 0) { + d_printf("net_witness_scan_registrations() failed\n"); + goto out; + } + + ret = 0; +out: + TALLOC_FREE(frame); + return ret; +} + +struct net_witness_client_move_state { + struct net_context *c; + struct rpcd_witness_registration_updateB m; + char *headline; +}; + +static bool net_witness_client_move_prepare_fn(void *private_data) +{ + struct net_witness_client_move_state *state = + (struct net_witness_client_move_state *)private_data; + + if (state->headline != NULL) { + d_printf("%s\n", state->headline); + TALLOC_FREE(state->headline); + } + + return true; +} + +static bool net_witness_client_move_match_fn(void *private_data, + const struct rpcd_witness_registration *rg) +{ + return true; +} + +static NTSTATUS net_witness_client_move_process_fn(void *private_data, + const struct rpcd_witness_registration *rg) +{ + struct net_witness_client_move_state *state = + (struct net_witness_client_move_state *)private_data; + struct net_context *c = state->c; + struct rpcd_witness_registration_updateB update = { + .context_handle = rg->context_handle, + .type = state->m.type, + .update = state->m.update, + }; + DATA_BLOB blob = { .length = 0, }; + enum ndr_err_code ndr_err; + NTSTATUS status; + + if (state->headline != NULL) { + d_printf("%s\n", state->headline); + TALLOC_FREE(state->headline); + } + + SMB_ASSERT(update.type != 0); + + if (DEBUGLVL(DBGLVL_DEBUG)) { + NDR_PRINT_DEBUG(rpcd_witness_registration_updateB, &update); + } + + ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &update, + (ndr_push_flags_fn_t)ndr_push_rpcd_witness_registration_updateB); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DBG_ERR("ndr_push_struct_blob - %s\n", nt_errstr(status)); + return status; + } + + status = messaging_send(c->msg_ctx, + rg->server_id, + MSG_RPCD_WITNESS_REGISTRATION_UPDATE, + &blob); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("messaging_send() - %s\n", nt_errstr(status)); + return status; + } + + return NT_STATUS_OK; +} + +static void net_witness_update_usage(void) +{ + d_printf(" If the update should be applied to all registrations\n" + " it needs to be explicitly specified:\n" + "\n" + " --witness-apply-to-all\n" + " This selects all registrations.\n" + " Note: This is mutual exclusive to " + "the above options.\n" + "\n"); +} + +static bool net_witness_verify_update_options(struct net_context *c) +{ + if (c->opt_witness_registration == NULL && + c->opt_witness_net_name == NULL && + c->opt_witness_share_name == NULL && + c->opt_witness_ip_address == NULL && + c->opt_witness_client_computer_name == NULL && + c->opt_witness_apply_to_all == 0) + { + d_printf("--witness-apply-to-all or " + "at least one of following requires:\n" + "--witness-registration\n" + "--witness-net-name\n" + "--witness-share-name\n" + "--witness-ip-address\n" + "--witness-client-computer-name\n"); + return false; + } + + if (c->opt_witness_apply_to_all == 0) { + return true; + } + + if (c->opt_witness_registration != NULL || + c->opt_witness_net_name != NULL || + c->opt_witness_share_name != NULL || + c->opt_witness_ip_address != NULL || + c->opt_witness_client_computer_name != NULL) + { + d_printf("--witness-apply-to-all not allowed " + "together with the following options:\n" + "--witness-registration\n" + "--witness-net-name\n" + "--witness-share-name\n" + "--witness-ip-address\n" + "--witness-client-computer-name\n"); + return false; + } + + return true; +} + +static void net_witness_move_usage(const char *name) +{ + d_printf(" The content of the %s notification contains ip addresses\n" + " specified by (exactly one) of the following options:\n" + "\n" + " --witness-new-node=NODEID\n" + " By specifying a NODEID all ip addresses\n" + " currently available on the given node are\n" + " included in the response.\n" + " By specifying '-1' as NODEID all ip addresses\n" + " of the cluster are included in the response.\n" + "\n" + " --witness-new-ip=IPADDRESS\n" + " By specifying an IPADDRESS only the specified\n" + " ip address is included in the response.\n" + "\n", + name); +} + +static bool net_witness_verify_move_options(struct net_context *c, + uint32_t *new_node, + bool *is_ipv4, + bool *is_ipv6) +{ + bool ok; + + *new_node = NONCLUSTER_VNN; + *is_ipv4 = false; + *is_ipv6 = false; + + ok = net_witness_verify_update_options(c); + if (!ok) { + return false; + } + + if (c->opt_witness_new_ip != NULL && + c->opt_witness_new_node != -2) + { + d_printf("--witness-new-ip and " + "--witness-new-node are not allowed together\n"); + return false; + } + + if (c->opt_witness_new_ip == NULL && + c->opt_witness_new_node == -2) + { + d_printf("--witness-new-ip or --witness-new-node required\n"); + return false; + } + + if (c->opt_witness_new_node != -2) { + *new_node = c->opt_witness_new_node; + return true; + } + + if (is_ipaddress_v4(c->opt_witness_new_ip)) { + *is_ipv4 = true; + return true; + } + + if (is_ipaddress_v6(c->opt_witness_new_ip)) { + *is_ipv6 = true; + return true; + } + + d_printf("Invalid ip address for --witness-new-ip=%s\n", + c->opt_witness_new_ip); + return false; +} + +#ifdef HAVE_JANSSON +static bool net_witness_move_message_json(struct net_context *c, + const char *msg_type, + struct json_object *pmessage_json) +{ + struct json_object message_json = json_empty_object; + int ret; + + message_json = json_new_object(); + if (json_is_invalid(&message_json)) { + return false; + } + + ret = json_add_string(&message_json, + "type", + msg_type); + if (ret != 0) { + json_free(&message_json); + return false; + } + + if (c->opt_witness_new_ip != NULL) { + ret = json_add_string(&message_json, + "new_ip", + c->opt_witness_new_ip); + if (ret != 0) { + return false; + } + } else if (c->opt_witness_new_node != -1) { + ret = json_add_int(&message_json, + "new_node", + c->opt_witness_new_node); + if (ret != 0) { + return false; + } + } else { + ret = json_add_bool(&message_json, + "all_nodes", + true); + if (ret != 0) { + return false; + } + } + + *pmessage_json = message_json; + return true; +} +#endif /* HAVE_JANSSON */ + +static void net_witness_client_move_usage(void) +{ + d_printf("%s\n" + "net witness client-move\n" + " %s\n\n", + _("Usage:"), + _("Generate client move notifications for " + "witness registrations to a new ip or node")); + net_witness_filter_usage(); + net_witness_update_usage(); + net_witness_move_usage("CLIENT_MOVE"); +} + +static int net_witness_client_move(struct net_context *c, int argc, const char **argv) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct net_witness_client_move_state state = { .c = c, }; + struct rpcd_witness_registration_updateB *m = &state.m; +#ifdef HAVE_JANSSON + struct json_object _message_json = json_empty_object; +#endif /* HAVE_JANSSON */ + struct json_object *message_json = NULL; + struct net_witness_scan_registrations_action_state action = { + .prepare_fn = net_witness_client_move_prepare_fn, + .match_fn = net_witness_client_move_match_fn, + .process_fn = net_witness_client_move_process_fn, + .private_data = &state, + }; + int ret = -1; + const char *msg_type = NULL; + uint32_t new_node = NONCLUSTER_VNN; + bool is_ipv4 = false; + bool is_ipv6 = false; + bool ok; + + if (c->display_usage) { + net_witness_client_move_usage(); + goto out; + } + + if (argc != 0) { + net_witness_client_move_usage(); + goto out; + } + + if (!lp_clustering()) { + d_printf("ERROR: Only supported with clustering=yes!\n\n"); + goto out; + } + + ok = net_witness_verify_move_options(c, &new_node, &is_ipv4, &is_ipv6); + if (!ok) { + goto out; + } + + if (is_ipv4) { + m->type = RPCD_WITNESS_REGISTRATION_UPDATE_CLIENT_MOVE_TO_IPV4; + m->update.client_move_to_ipv4.new_ipv4 = c->opt_witness_new_ip; + msg_type = "CLIENT_MOVE_TO_IPV4"; + state.headline = talloc_asprintf(frame, + "CLIENT_MOVE_TO_IPV4: %s", + c->opt_witness_new_ip); + if (state.headline == NULL) { + goto out; + } + } else if (is_ipv6) { + m->type = RPCD_WITNESS_REGISTRATION_UPDATE_CLIENT_MOVE_TO_IPV6; + m->update.client_move_to_ipv6.new_ipv6 = c->opt_witness_new_ip; + msg_type = "CLIENT_MOVE_TO_IPV6"; + state.headline = talloc_asprintf(frame, + "CLIENT_MOVE_TO_IPV6: %s", + c->opt_witness_new_ip); + if (state.headline == NULL) { + goto out; + } + } else if (new_node != NONCLUSTER_VNN) { + m->type = RPCD_WITNESS_REGISTRATION_UPDATE_CLIENT_MOVE_TO_NODE; + m->update.client_move_to_node.new_node = new_node; + msg_type = "CLIENT_MOVE_TO_NODE"; + state.headline = talloc_asprintf(frame, + "CLIENT_MOVE_TO_NODE: %u", + new_node); + if (state.headline == NULL) { + goto out; + } + } else { + m->type = RPCD_WITNESS_REGISTRATION_UPDATE_CLIENT_MOVE_TO_NODE; + m->update.client_move_to_node.new_node = NONCLUSTER_VNN; + msg_type = "CLIENT_MOVE_TO_NODE"; + state.headline = talloc_asprintf(frame, + "CLIENT_MOVE_TO_NODE: ALL"); + if (state.headline == NULL) { + goto out; + } + } + +#ifdef HAVE_JANSSON + if (c->opt_json) { + TALLOC_FREE(state.headline); + + ok = net_witness_move_message_json(c, + msg_type, + &_message_json); + if (!ok) { + d_printf("net_witness_move_message_json(%s) failed\n", + msg_type); + goto out; + } + + message_json = &_message_json; + } +#else /* not HAVE_JANSSON */ + (void)msg_type; +#endif /* not HAVE_JANSSON */ + + ret = net_witness_scan_registrations(c, message_json, &action); + if (ret != 0) { + d_printf("net_witness_scan_registrations() failed\n"); + goto out; + } + + ret = 0; +out: +#ifdef HAVE_JANSSON + if (!json_is_invalid(&_message_json)) { + json_free(&_message_json); + } +#endif /* HAVE_JANSSON */ + TALLOC_FREE(frame); + return ret; +} + +struct net_witness_share_move_state { + struct net_context *c; + struct rpcd_witness_registration_updateB m; + char *headline; +}; + +static bool net_witness_share_move_prepare_fn(void *private_data) +{ + struct net_witness_share_move_state *state = + (struct net_witness_share_move_state *)private_data; + + if (state->headline != NULL) { + d_printf("%s\n", state->headline); + TALLOC_FREE(state->headline); + } + + return true; +} + +static bool net_witness_share_move_match_fn(void *private_data, + const struct rpcd_witness_registration *rg) +{ + if (rg->share_name == NULL) { + return false; + } + + return true; +} + +static NTSTATUS net_witness_share_move_process_fn(void *private_data, + const struct rpcd_witness_registration *rg) +{ + struct net_witness_share_move_state *state = + (struct net_witness_share_move_state *)private_data; + struct net_context *c = state->c; + struct rpcd_witness_registration_updateB update = { + .context_handle = rg->context_handle, + .type = state->m.type, + .update = state->m.update, + }; + DATA_BLOB blob = { .length = 0, }; + enum ndr_err_code ndr_err; + NTSTATUS status; + + SMB_ASSERT(update.type != 0); + + if (DEBUGLVL(DBGLVL_DEBUG)) { + NDR_PRINT_DEBUG(rpcd_witness_registration_updateB, &update); + } + + ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &update, + (ndr_push_flags_fn_t)ndr_push_rpcd_witness_registration_updateB); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DBG_ERR("ndr_push_struct_blob - %s\n", nt_errstr(status)); + return status; + } + + status = messaging_send(c->msg_ctx, + rg->server_id, + MSG_RPCD_WITNESS_REGISTRATION_UPDATE, + &blob); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("messaging_send() - %s\n", nt_errstr(status)); + return status; + } + + return NT_STATUS_OK; +} + +static void net_witness_share_move_usage(void) +{ + d_printf("%s\n" + "net witness share-move\n" + " %s\n\n", + _("Usage:"), + _("Generate share move notifications for " + "witness registrations to a new ip or node")); + net_witness_filter_usage(); + net_witness_update_usage(); + d_printf(" Note: This only applies to registrations with " + "a non empty share name!\n\n"); + net_witness_move_usage("SHARE_MOVE"); +} + +static int net_witness_share_move(struct net_context *c, int argc, const char **argv) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct net_witness_share_move_state state = { .c = c, }; + struct rpcd_witness_registration_updateB *m = &state.m; +#ifdef HAVE_JANSSON + struct json_object _message_json = json_empty_object; +#endif /* HAVE_JANSSON */ + struct json_object *message_json = NULL; + struct net_witness_scan_registrations_action_state action = { + .prepare_fn = net_witness_share_move_prepare_fn, + .match_fn = net_witness_share_move_match_fn, + .process_fn = net_witness_share_move_process_fn, + .private_data = &state, + }; + int ret = -1; + const char *msg_type = NULL; + uint32_t new_node = NONCLUSTER_VNN; + bool is_ipv4 = false; + bool is_ipv6 = false; + bool ok; + + if (c->display_usage) { + net_witness_share_move_usage(); + goto out; + } + + if (argc != 0) { + net_witness_share_move_usage(); + goto out; + } + + if (!lp_clustering()) { + d_printf("ERROR: Only supported with clustering=yes!\n\n"); + goto out; + } + + ok = net_witness_verify_move_options(c, &new_node, &is_ipv4, &is_ipv6); + if (!ok) { + goto out; + } + + if (is_ipv4) { + m->type = RPCD_WITNESS_REGISTRATION_UPDATE_SHARE_MOVE_TO_IPV4; + m->update.share_move_to_ipv4.new_ipv4 = c->opt_witness_new_ip; + msg_type = "SHARE_MOVE_TO_IPV4"; + state.headline = talloc_asprintf(frame, + "SHARE_MOVE_TO_IPV4: %s", + c->opt_witness_new_ip); + if (state.headline == NULL) { + goto out; + } + } else if (is_ipv6) { + m->type = RPCD_WITNESS_REGISTRATION_UPDATE_SHARE_MOVE_TO_IPV6; + m->update.share_move_to_ipv6.new_ipv6 = c->opt_witness_new_ip; + msg_type = "SHARE_MOVE_TO_IPV6"; + state.headline = talloc_asprintf(frame, + "SHARE_MOVE_TO_IPV6: %s", + c->opt_witness_new_ip); + if (state.headline == NULL) { + goto out; + } + } else if (new_node != NONCLUSTER_VNN) { + m->type = RPCD_WITNESS_REGISTRATION_UPDATE_SHARE_MOVE_TO_NODE; + m->update.share_move_to_node.new_node = new_node; + msg_type = "SHARE_MOVE_TO_NODE"; + state.headline = talloc_asprintf(frame, + "SHARE_MOVE_TO_NODE: %u", + new_node); + if (state.headline == NULL) { + goto out; + } + } else { + m->type = RPCD_WITNESS_REGISTRATION_UPDATE_SHARE_MOVE_TO_NODE; + m->update.share_move_to_node.new_node = NONCLUSTER_VNN; + msg_type = "SHARE_MOVE_TO_NODE"; + state.headline = talloc_asprintf(frame, + "SHARE_MOVE_TO_NODE: ALL"); + if (state.headline == NULL) { + goto out; + } + } + +#ifdef HAVE_JANSSON + if (c->opt_json) { + TALLOC_FREE(state.headline); + + ok = net_witness_move_message_json(c, + msg_type, + &_message_json); + if (!ok) { + d_printf("net_witness_move_message_json(%s) failed\n", + msg_type); + goto out; + } + + message_json = &_message_json; + } +#else /* not HAVE_JANSSON */ + (void)msg_type; +#endif /* not HAVE_JANSSON */ + + ret = net_witness_scan_registrations(c, message_json, &action); + if (ret != 0) { + d_printf("net_witness_scan_registrations() failed\n"); + goto out; + } + + ret = 0; +out: +#ifdef HAVE_JANSSON + if (!json_is_invalid(&_message_json)) { + json_free(&_message_json); + } +#endif /* HAVE_JANSSON */ + TALLOC_FREE(frame); + return ret; +} + +struct net_witness_force_unregister_state { + struct net_context *c; + struct rpcd_witness_registration_updateB m; + char *headline; +}; + +static bool net_witness_force_unregister_prepare_fn(void *private_data) +{ + struct net_witness_force_unregister_state *state = + (struct net_witness_force_unregister_state *)private_data; + + if (state->headline != NULL) { + d_printf("%s\n", state->headline); + TALLOC_FREE(state->headline); + } + + return true; +} + +static bool net_witness_force_unregister_match_fn(void *private_data, + const struct rpcd_witness_registration *rg) +{ + return true; +} + +static NTSTATUS net_witness_force_unregister_process_fn(void *private_data, + const struct rpcd_witness_registration *rg) +{ + struct net_witness_force_unregister_state *state = + (struct net_witness_force_unregister_state *)private_data; + struct net_context *c = state->c; + struct rpcd_witness_registration_updateB update = { + .context_handle = rg->context_handle, + .type = state->m.type, + .update = state->m.update, + }; + DATA_BLOB blob = { .length = 0, }; + enum ndr_err_code ndr_err; + NTSTATUS status; + + SMB_ASSERT(update.type != 0); + + if (DEBUGLVL(DBGLVL_DEBUG)) { + NDR_PRINT_DEBUG(rpcd_witness_registration_updateB, &update); + } + + ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &update, + (ndr_push_flags_fn_t)ndr_push_rpcd_witness_registration_updateB); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DBG_ERR("ndr_push_struct_blob - %s\n", nt_errstr(status)); + return status; + } + + status = messaging_send(c->msg_ctx, + rg->server_id, + MSG_RPCD_WITNESS_REGISTRATION_UPDATE, + &blob); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("messaging_send() - %s\n", nt_errstr(status)); + return status; + } + + return NT_STATUS_OK; +} + +static void net_witness_force_unregister_usage(void) +{ + d_printf("%s\n" + "net witness force-unregister\n" + " %s\n\n", + _("Usage:"), + _("Force unregistrations for witness registrations")); + net_witness_filter_usage(); + net_witness_update_usage(); + d_printf(" The selected registrations are removed on " + "the server and\n" + " any pending AsyncNotify request will get " + "a NOT_FOUND error.\n" + "\n" + " Typically this triggers a clean re-registration " + "on the client.\n" + "\n"); +} + +static int net_witness_force_unregister(struct net_context *c, int argc, const char **argv) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct net_witness_force_unregister_state state = { .c = c, }; + struct rpcd_witness_registration_updateB *m = &state.m; +#ifdef HAVE_JANSSON + struct json_object _message_json = json_empty_object; +#endif /* HAVE_JANSSON */ + struct json_object *message_json = NULL; + struct net_witness_scan_registrations_action_state action = { + .prepare_fn = net_witness_force_unregister_prepare_fn, + .match_fn = net_witness_force_unregister_match_fn, + .process_fn = net_witness_force_unregister_process_fn, + .private_data = &state, + }; + int ret = -1; + bool ok; + + if (c->display_usage) { + net_witness_force_unregister_usage(); + goto out; + } + + if (argc != 0) { + net_witness_force_unregister_usage(); + goto out; + } + + if (!lp_clustering()) { + d_printf("ERROR: Only supported with clustering=yes!\n\n"); + goto out; + } + + ok = net_witness_verify_update_options(c); + if (!ok) { + goto out; + } + + m->type = RPCD_WITNESS_REGISTRATION_UPDATE_FORCE_UNREGISTER; + + state.headline = talloc_asprintf(frame, "FORCE_UNREGISTER:"); + if (state.headline == NULL) { + goto out; + } + +#ifdef HAVE_JANSSON + if (c->opt_json) { + TALLOC_FREE(state.headline); + + _message_json = json_new_object(); + if (json_is_invalid(&_message_json)) { + goto out; + } + + ret = json_add_string(&_message_json, + "type", + "FORCE_UNREGISTER"); + if (ret != 0) { + goto out; + } + + message_json = &_message_json; + } +#endif /* HAVE_JANSSON */ + + ret = net_witness_scan_registrations(c, message_json, &action); + if (ret != 0) { + d_printf("net_witness_scan_registrations() failed\n"); + goto out; + } + + ret = 0; +out: +#ifdef HAVE_JANSSON + if (!json_is_invalid(&_message_json)) { + json_free(&_message_json); + } +#endif /* HAVE_JANSSON */ + TALLOC_FREE(frame); + return ret; +} + +struct net_witness_force_response_state { + struct net_context *c; + struct rpcd_witness_registration_updateB m; +#ifdef HAVE_JANSSON + struct json_object json_root; +#endif /* HAVE_JANSSON */ + char *headline; +}; + +#ifdef HAVE_JANSSON +static NTSTATUS net_witness_force_response_parse_rc( + struct net_witness_force_response_state *state, + json_t *jsmsg, + TALLOC_CTX *mem_ctx, + size_t mi, + union witness_notifyResponse_message *message) +{ + struct witness_ResourceChange *rc = &message->resource_change; + json_t *jsctype = NULL; + json_int_t ctype; + json_t *jscname = NULL; + const char *cname = NULL; + + if (!json_is_object(jsmsg)) { + DBG_ERR("'message[%zu]' needs to be an object\n", mi); + return NT_STATUS_INVALID_PARAMETER; + } + + jsctype = json_object_get(jsmsg, "type"); + if (jsctype == NULL) { + DBG_ERR("%s: INVALID_PARAMETER\n", __location__); + return NT_STATUS_INVALID_PARAMETER; + } + if (!json_is_integer(jsctype)) { + DBG_ERR("%s: INVALID_PARAMETER\n", __location__); + return NT_STATUS_INVALID_PARAMETER; + } + ctype = json_integer_value(jsctype); + + jscname = json_object_get(jsmsg, "name"); + if (jscname == NULL) { + DBG_ERR("%s: INVALID_PARAMETER\n", __location__); + return NT_STATUS_INVALID_PARAMETER; + } + if (!json_is_string(jscname)) { + DBG_ERR("%s: INVALID_PARAMETER\n", __location__); + return NT_STATUS_INVALID_PARAMETER; + } + cname = json_string_value(jscname); + + rc->type = ctype; + rc->name = talloc_strdup(mem_ctx, cname); + if (rc->name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +static NTSTATUS net_witness_force_response_parse_ipl( + struct net_witness_force_response_state *state, + json_t *jsmsg, + TALLOC_CTX *mem_ctx, + size_t mi, + union witness_notifyResponse_message *message) +{ + struct witness_IPaddrInfoList *ipl = + &message->client_move; + size_t ai, num_addrs = 0; + struct witness_IPaddrInfo *addrs = NULL; + + if (!json_is_array(jsmsg)) { + DBG_ERR("'messages[%zu]' needs to be an array\n", mi); + return NT_STATUS_INVALID_PARAMETER; + } + + num_addrs = json_array_size(jsmsg); + if (num_addrs > UINT32_MAX) { + DBG_ERR("Too many elements in 'messages[%zu]': %zu\n", + mi, num_addrs); + return NT_STATUS_INVALID_PARAMETER; + } + + addrs = talloc_zero_array(mem_ctx, + struct witness_IPaddrInfo, + num_addrs); + if (addrs == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (ai = 0; ai < num_addrs; ai++) { + struct witness_IPaddrInfo *info = + &addrs[ai]; + json_t *jsaddr = json_array_get(jsmsg, ai); + json_t *jsflags = NULL; + json_int_t flags; + json_t *jsipv4 = NULL; + const char *ipv4 = NULL; + json_t *jsipv6 = NULL; + const char *ipv6 = NULL; + + if (!json_is_object(jsaddr)) { + DBG_ERR("'messages[%zu][%zu]' needs to be an object\n", + mi, ai); + return NT_STATUS_INVALID_PARAMETER; + } + + jsflags = json_object_get(jsaddr, "flags"); + if (jsflags == NULL) { + DBG_ERR("'messages[%zu][%zu]['flags']' missing\n", + mi, ai); + return NT_STATUS_INVALID_PARAMETER; + } + if (!json_is_integer(jsflags)) { + DBG_ERR("'messages[%zu][%zu]['flags']' " + "needs to be an integer\n", + mi, ai); + return NT_STATUS_INVALID_PARAMETER; + } + flags = json_integer_value(jsflags); + + jsipv4 = json_object_get(jsaddr, "ipv4"); + if (jsipv4 != NULL) { + if (!json_is_string(jsipv4)) { + DBG_ERR("'messages[%zu][%zu]['ipv4']' " + "needs to be a string\n", + mi, ai); + return NT_STATUS_INVALID_PARAMETER; + } + ipv4 = json_string_value(jsipv4); + if (!is_ipaddress_v4(ipv4)) { + DBG_ERR("'messages[%zu][%zu]['ipv4']' " + "needs to be a valid ipv4 address\n", + mi, ai); + return NT_STATUS_INVALID_PARAMETER; + } + } else { + ipv4 = "0.0.0.0"; + } + + jsipv6 = json_object_get(jsaddr, "ipv6"); + if (jsipv6 != NULL) { + if (!json_is_string(jsipv6)) { + DBG_ERR("'messages[%zu][%zu]['ipv6']' " + "needs to be a string\n", + mi, ai); + DBG_ERR("%s: INVALID_PARAMETER\n", __location__); + return NT_STATUS_INVALID_PARAMETER; + } + ipv6 = json_string_value(jsipv6); + if (!is_ipaddress_v6(ipv6)) { + DBG_ERR("'messages[%zu][%zu]['ipv4']' " + "needs to be a valid ipv6 address\n", + mi, ai); + return NT_STATUS_INVALID_PARAMETER; + } + } else { + ipv6 = "::"; + } + + info->flags = flags; + info->ipv4 = talloc_strdup(addrs, ipv4); + if (info->ipv4 == NULL) { + return NT_STATUS_NO_MEMORY; + } + info->ipv6 = talloc_strdup(addrs, ipv6); + if (info->ipv6 == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + ipl->num = num_addrs; + ipl->addr = addrs; + + return NT_STATUS_OK; +} +#endif /* HAVE_JANSSON */ + +static NTSTATUS net_witness_force_response_parse(struct net_witness_force_response_state *state) +{ +#ifdef HAVE_JANSSON + struct net_context *c = state->c; + struct rpcd_witness_registration_update_force_response *force = NULL; + struct witness_notifyResponse *response = NULL; + size_t mi, num_messages = 0; + union witness_notifyResponse_message *messages = NULL; + json_t *jsroot = NULL; + json_t *jsresult = NULL; + json_t *jsresponse = NULL; + json_t *jstype = NULL; + json_t *jsmessages = NULL; + + if (c->opt_witness_forced_response != NULL) { + const char *str = c->opt_witness_forced_response; + size_t flags = JSON_REJECT_DUPLICATES; + json_error_t jserror; + + jsroot = json_loads(str, flags, &jserror); + if (jsroot == NULL) { + DBG_ERR("Invalid JSON in " + "--witness-forced-response='%s'\n", + str); + return NT_STATUS_INVALID_PARAMETER; + } + state->json_root = (struct json_object) { + .root = jsroot, + .valid = true, + }; + } + + state->m.type = RPCD_WITNESS_REGISTRATION_UPDATE_FORCE_RESPONSE; + force = &state->m.update.force_response; + force->response = NULL; + force->result = WERR_OK; + + if (jsroot == NULL) { + return NT_STATUS_OK; + } + + jsresult = json_object_get(jsroot, "result"); + if (jsresult != NULL) { + int val_type = json_typeof(jsresult); + + switch (val_type) { + case JSON_INTEGER: { + json_int_t val = json_integer_value(jsresult); + + if (val > UINT32_MAX) { + DBG_ERR("Invalid 'result' value: %d\n", + (int) val); + return NT_STATUS_INVALID_PARAMETER; + } + if (val < 0) { + DBG_ERR("invalid 'result' value: %d\n", + (int) val); + return NT_STATUS_INVALID_PARAMETER; + } + + force->result = W_ERROR(val); + }; break; + default: + DBG_ERR("Invalid json type for 'result' - needs integer\n"); + return NT_STATUS_INVALID_PARAMETER; + } + } + + jsresponse = json_object_get(jsroot, "response"); + if (jsresponse == NULL) { + return NT_STATUS_OK; + } + + if (!json_is_object(jsresponse)) { + DBG_ERR("Invalid json type 'response' needs object\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + response = talloc_zero(talloc_tos(), struct witness_notifyResponse); + if (response == NULL) { + return NT_STATUS_NO_MEMORY; + } + + jstype = json_object_get(jsresponse, "type"); + if (jstype == NULL) { + DBG_ERR("Missing 'type' element in 'response'\n"); + return NT_STATUS_INVALID_PARAMETER; + } + { + int val_type = json_typeof(jstype); + + switch (val_type) { + case JSON_INTEGER: { + json_int_t val = json_integer_value(jstype); + + if (val > WITNESS_NOTIFY_IP_CHANGE) { + DBG_ERR("invalid 'type' value in 'response': " + "%d\n", (int) val); + return NT_STATUS_INVALID_PARAMETER; + } + if (val < WITNESS_NOTIFY_RESOURCE_CHANGE) { + DBG_ERR("invalid 'type' value in 'response': " + "%d\n", (int) val); + return NT_STATUS_INVALID_PARAMETER; + } + + response->type = val; + }; break; + default: + DBG_ERR("Invalid json type for 'type' in 'response' " + "- needs integer\n"); + return NT_STATUS_INVALID_PARAMETER; + } + } + + force->response = response; + + jsmessages = json_object_get(jsresponse, "messages"); + if (jsmessages == NULL) { + return NT_STATUS_OK; + } + + if (!json_is_array(jsmessages)) { + DBG_ERR("'messages' in 'response' needs to be an array\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + num_messages = json_array_size(jsmessages); + if (num_messages > UINT32_MAX) { + DBG_ERR("Too many elements in 'messages': %zu\n", + num_messages); + return NT_STATUS_INVALID_PARAMETER; + } + + messages = talloc_zero_array(response, + union witness_notifyResponse_message, + num_messages); + if (messages == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (mi = 0; mi < num_messages; mi++) { + json_t *jsmsg = json_array_get(jsmessages, mi); + union witness_notifyResponse_message *message = &messages[mi]; + NTSTATUS status; + + switch (response->type) { + case WITNESS_NOTIFY_RESOURCE_CHANGE: + status = net_witness_force_response_parse_rc(state, + jsmsg, + messages, + mi, + message); + if (!NT_STATUS_IS_OK(status)) { + const char *fn = + "net_witness_force_response_parse_rc"; + DBG_ERR("%s failed: %s\n", + fn, nt_errstr(status)); + return status; + } + + break; + case WITNESS_NOTIFY_CLIENT_MOVE: + case WITNESS_NOTIFY_SHARE_MOVE: + case WITNESS_NOTIFY_IP_CHANGE: + status = net_witness_force_response_parse_ipl(state, + jsmsg, + messages, + mi, + message); + if (!NT_STATUS_IS_OK(status)) { + const char *fn = + "net_witness_force_response_parse_ipl"; + DBG_ERR("%s failed: %s\n", + fn, nt_errstr(status)); + return status; + } + + break; + } + } + + response->num = num_messages; + response->messages = messages; + + return NT_STATUS_OK; +#else /* not HAVE_JANSSON */ + d_fprintf(stderr, _("JSON support not available\n")); + return NT_STATUS_NOT_IMPLEMENTED; +#endif /* not HAVE_JANSSON */ +} + +static bool net_witness_force_response_prepare_fn(void *private_data) +{ + struct net_witness_force_response_state *state = + (struct net_witness_force_response_state *)private_data; + + if (state->headline != NULL) { + d_printf("%s\n", state->headline); + TALLOC_FREE(state->headline); + } + + return true; +} + +static bool net_witness_force_response_match_fn(void *private_data, + const struct rpcd_witness_registration *rg) +{ + return true; +} + +static NTSTATUS net_witness_force_response_process_fn(void *private_data, + const struct rpcd_witness_registration *rg) +{ + struct net_witness_force_response_state *state = + (struct net_witness_force_response_state *)private_data; + struct net_context *c = state->c; + struct rpcd_witness_registration_updateB update = { + .context_handle = rg->context_handle, + .type = state->m.type, + .update = state->m.update, + }; + DATA_BLOB blob = { .length = 0, }; + enum ndr_err_code ndr_err; + NTSTATUS status; + + SMB_ASSERT(update.type != 0); + + if (DEBUGLVL(DBGLVL_DEBUG)) { + NDR_PRINT_DEBUG(rpcd_witness_registration_updateB, &update); + } + + ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &update, + (ndr_push_flags_fn_t)ndr_push_rpcd_witness_registration_updateB); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DBG_ERR("ndr_push_struct_blob - %s\n", nt_errstr(status)); + return status; + } + + status = messaging_send(c->msg_ctx, + rg->server_id, + MSG_RPCD_WITNESS_REGISTRATION_UPDATE, + &blob); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("messaging_send() - %s\n", nt_errstr(status)); + return status; + } + + return NT_STATUS_OK; +} + +static void net_witness_force_response_usage(void) +{ + d_printf("%s\n" + "net witness force-response\n" + " %s\n\n", + _("Usage:"), + _("Force an AsyncNotify response based on " + "json input (mostly for testing)")); + net_witness_filter_usage(); + net_witness_update_usage(); + d_printf(" Note this is designed for testing and debugging!\n" + "\n" + " In short it is not designed to be used by " + "administrators,\n" + " but developers and automated tests.\n" + "\n" + " By default an empty response with WERR_OK is generated,\n" + " but basically any valid response can be specified by a\n" + " specifying a JSON string:\n" + "\n" + " --witness-forced-response=JSON\n" + " This allows the generation of very complex\n" + " witness_notifyResponse structures.\n" + "\n" + " As this is for developers, please read the code\n" + " in order to understand all possible values\n" + " of the JSON string format...\n" + "\n" + " Simple examples are:\n" + "\n" + "# Resource Change:\n%s\n" + "\n" + "# Client Move:\n%s\n" + "\n" + "# Share Move:\n%s\n" + "\n" + "# IP Change:\n%s\n" + "\n", + "'{ \"result\": 0, \"response\": { \"type\": 1, " + "\"messages\": [ { " + "\"type\": 255 , " + "\"name\": \"some-resource-name\" " + "} ]" + "}}'", + "'{ \"result\": 0, \"response\": { \"type\": 2, " + "\"messages\": [" + "[{ " + "\"flags\": 9, " + "\"ipv4\": \"10.0.10.1\" " + "}]" + "]" + "}}'", + "'{ \"result\": 0, \"response\": { \"type\": 3, " + "\"messages\": [" + "[{ " + "\"flags\": 9, " + "\"ipv4\": \"10.0.10.1\" " + "}]" + "]" + "}}'", + "'{ \"result\": 0, \"response\": { \"type\": 4, " + "\"messages\": [" + "[{ " + "\"flags\": 9, " + "\"ipv4\": \"10.0.10.1\" " + "}]" + "]" + "}}'"); +} + +static int net_witness_force_response(struct net_context *c, int argc, const char **argv) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct net_witness_force_response_state state = { .c = c, }; +#ifdef HAVE_JANSSON + struct json_object _message_json = json_empty_object; +#endif /* HAVE_JANSSON */ + struct json_object *message_json = NULL; + struct net_witness_scan_registrations_action_state action = { + .prepare_fn = net_witness_force_response_prepare_fn, + .match_fn = net_witness_force_response_match_fn, + .process_fn = net_witness_force_response_process_fn, + .private_data = &state, + }; + NTSTATUS status; + int ret = -1; + bool ok; + + if (c->display_usage) { + net_witness_force_response_usage(); + goto out; + } + + if (argc != 0) { + net_witness_force_response_usage(); + goto out; + } + + if (!lp_clustering()) { + d_printf("ERROR: Only supported with clustering=yes!\n\n"); + goto out; + } + + ok = net_witness_verify_update_options(c); + if (!ok) { + goto out; + } + + status = net_witness_force_response_parse(&state); + if (!NT_STATUS_IS_OK(status)) { + d_printf("net_witness_force_response_parse failed: %s\n", + nt_errstr(status)); + goto out; + } + + state.headline = talloc_asprintf(frame, "FORCE_RESPONSE:%s%s", + c->opt_witness_forced_response != NULL ? + " " : "", + c->opt_witness_forced_response != NULL ? + c->opt_witness_forced_response : ""); + + if (state.headline == NULL) { + goto out; + } + +#ifdef HAVE_JANSSON + if (c->opt_json) { + TALLOC_FREE(state.headline); + + _message_json = json_new_object(); + if (json_is_invalid(&_message_json)) { + goto out; + } + + ret = json_add_string(&_message_json, + "type", + "FORCE_RESPONSE"); + if (ret != 0) { + goto out; + } + + if (!json_is_invalid(&state.json_root)) { + ret = json_add_object(&_message_json, + "json", + &state.json_root); + if (ret != 0) { + goto out; + } + state.json_root = json_empty_object; + } + message_json = &_message_json; + } +#endif /* HAVE_JANSSON */ + + ret = net_witness_scan_registrations(c, message_json, &action); + if (ret != 0) { + d_printf("net_witness_scan_registrations() failed\n"); + goto out; + } + + ret = 0; +out: +#ifdef HAVE_JANSSON + if (!json_is_invalid(&_message_json)) { + json_free(&_message_json); + } + if (!json_is_invalid(&state.json_root)) { + json_free(&state.json_root); + } +#endif /* HAVE_JANSSON */ + TALLOC_FREE(frame); + return ret; +} + +int net_witness(struct net_context *c, int argc, const char **argv) +{ + struct functable func[] = { + { + "list", + net_witness_list, + NET_TRANSPORT_LOCAL, + N_("List witness registrations " + "from rpcd_witness_registration.tdb"), + N_("net witness list\n" + " List witness registrations " + "from rpcd_witness_registration.tdb"), + }, + { + "client-move", + net_witness_client_move, + NET_TRANSPORT_LOCAL, + N_("Generate client move notifications for " + "witness registrations to a new ip or node"), + N_("net witness client-move\n" + " Generate client move notifications for " + "witness registrations to a new ip or node"), + }, + { + "share-move", + net_witness_share_move, + NET_TRANSPORT_LOCAL, + N_("Generate share move notifications for " + "witness registrations to a new ip or node"), + N_("net witness share-move\n" + " Generate share move notifications for " + "witness registrations to a new ip or node"), + }, + { + "force-unregister", + net_witness_force_unregister, + NET_TRANSPORT_LOCAL, + N_("Force unregistrations for witness registrations"), + N_("net witness force-unregister\n" + " Force unregistrations for " + "witness registrations"), + }, + { + "force-response", + net_witness_force_response, + NET_TRANSPORT_LOCAL, + N_("Force an AsyncNotify response based on " + "json input (mostly for testing)"), + N_("net witness force-response\n" + " Force an AsyncNotify response based on " + "json input (mostly for testing)"), + }, + {NULL, NULL, 0, NULL, NULL} + }; + + return net_run_function(c, argc, argv, "net witness", func); +} |