s3:utils: add 'net witness force-response'
authorStefan Metzmacher <metze@samba.org>
Fri, 15 Dec 2023 13:49:37 +0000 (14:49 +0100)
committerStefan Metzmacher <metze@samba.org>
Tue, 23 Jan 2024 10:13:07 +0000 (11:13 +0100)
This allows generating any possible AsyncNotify response
for the specified selection of witness registrations
from rpcd_witness_registration.tdb.

This can be used by developers to test the (windows)
client behavior to specific AsyncNotify responses.

Signed-off-by: Stefan Metzmacher <metze@samba.org>
source3/utils/net.c
source3/utils/net.h
source3/utils/net_witness.c

index 0433eb04ba27548082106dd75f4531205eb2edf8..8b3451aaae9e05b7169843cc1d98d5ca38d2561c 100644 (file)
@@ -1289,6 +1289,12 @@ static struct functable net_func[] = {
                        .argInfo    = POPT_ARG_INT,
                        .arg        = &c->opt_witness_new_node,
                },
+               {
+                       .longName   = "witness-forced-response",
+                       .shortName  = 0,
+                       .argInfo    = POPT_ARG_STRING,
+                       .arg        = &c->opt_witness_forced_response,
+               },
                POPT_COMMON_SAMBA
                POPT_COMMON_CONNECTION
                POPT_COMMON_CREDENTIALS
index b53d0d5f32aa8c964a166a1fb4ba175000e2deb7..fca3a9953af2cae7e6ecac03cf3aa7682bc5cb1b 100644 (file)
@@ -99,6 +99,7 @@ struct net_context {
        int opt_witness_apply_to_all;
        const char *opt_witness_new_ip;
        int opt_witness_new_node;
+       const char *opt_witness_forced_response;
 
        int opt_have_ip;
        struct sockaddr_storage opt_dest_ip;
index 7c11c3877a3da4cfa05517307b6d779332ee2112..928efe19c5c3a626c57ef45761dbaab5c56d4923 100644 (file)
@@ -1698,6 +1698,610 @@ out:
        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[] = {
@@ -1740,6 +2344,16 @@ int net_witness(struct net_context *c, int argc, const char **argv)
                           "    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-reponse\n"
+                          "    Force an AsyncNotify response based on "
+                              "json input (mostly for testing)"),
+               },
                {NULL, NULL, 0, NULL, NULL}
        };