| // SPDX-License-Identifier: LGPL-2.1-or-later |
| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. |
| * |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <time.h> |
| |
| #include <ell/ell.h> |
| |
| #include "mesh/crypto.h" |
| #include "mesh/mesh.h" |
| #include "mesh/provision.h" |
| #include "mesh/error.h" |
| #include "mesh/dbus.h" |
| #include "mesh/agent.h" |
| |
| typedef enum { |
| MESH_AGENT_REQUEST_BLINK, |
| MESH_AGENT_REQUEST_BEEP, |
| MESH_AGENT_REQUEST_VIBRATE, |
| MESH_AGENT_REQUEST_OUT_NUMERIC, |
| MESH_AGENT_REQUEST_OUT_ALPHA, |
| MESH_AGENT_REQUEST_PUSH, |
| MESH_AGENT_REQUEST_TWIST, |
| MESH_AGENT_REQUEST_IN_NUMERIC, |
| MESH_AGENT_REQUEST_IN_ALPHA, |
| MESH_AGENT_REQUEST_STATIC_OOB, |
| MESH_AGENT_REQUEST_PRIVATE_KEY, |
| MESH_AGENT_REQUEST_PUBLIC_KEY, |
| MESH_AGENT_REQUEST_CAPABILITIES, |
| } agent_request_type_t; |
| |
| struct agent_request { |
| agent_request_type_t type; |
| struct l_dbus_message *msg; |
| void *cb; |
| void *user_data; |
| }; |
| |
| struct mesh_agent { |
| char *path; |
| char *owner; |
| struct mesh_agent_prov_caps caps; |
| struct agent_request *req; |
| }; |
| |
| struct prov_action { |
| const char *action; |
| uint16_t output; |
| uint16_t input; |
| uint8_t size; |
| }; |
| |
| struct oob_info { |
| const char *oob; |
| uint16_t mask; |
| }; |
| |
| static const struct prov_action cap_table[] = { |
| {"blink", 0x0001, 0x0000, 1}, |
| {"beep", 0x0002, 0x0000, 1}, |
| {"vibrate", 0x0004, 0x0000, 1}, |
| {"out-numeric", 0x0008, 0x0000, 8}, |
| {"out-alpha", 0x0010, 0x0000, 8}, |
| {"push", 0x0000, 0x0001, 1}, |
| {"twist", 0x0000, 0x0002, 1}, |
| {"in-numeric", 0x0000, 0x0004, 8}, |
| {"in-alpha", 0x0000, 0x0008, 8} |
| }; |
| |
| static const struct oob_info oob_table[] = { |
| {"other", 0x0001}, |
| {"uri", 0x0002}, |
| {"machine-code-2d", 0x0004}, |
| {"barcode", 0x0008}, |
| {"nfc", 0x0010}, |
| {"number", 0x0020}, |
| {"string", 0x0040}, |
| {"on-box", 0x0800}, |
| {"in-box", 0x1000}, |
| {"on-paper", 0x2000}, |
| {"in-manual", 0x4000}, |
| {"on-device", 0x8000} |
| }; |
| |
| static struct l_queue *agents; |
| |
| static bool simple_match(const void *a, const void *b) |
| { |
| return a == b; |
| } |
| |
| static bool parse_prov_caps(struct mesh_agent_prov_caps *caps, |
| struct l_dbus_message_iter *property) |
| { |
| struct l_dbus_message_iter iter_caps; |
| const char *str; |
| uint32_t i; |
| |
| if (!l_dbus_message_iter_get_variant(property, "as", &iter_caps)) |
| return false; |
| |
| while (l_dbus_message_iter_next_entry(&iter_caps, &str)) { |
| for (i = 0; i < L_ARRAY_SIZE(cap_table); i++) { |
| if (strcmp(str, cap_table[i].action)) |
| continue; |
| |
| caps->output_action |= cap_table[i].output; |
| if (cap_table[i].output && |
| caps->output_size < cap_table[i].size) |
| caps->output_size = cap_table[i].size; |
| |
| caps->input_action |= cap_table[i].input; |
| if (cap_table[i].input && |
| caps->input_size < cap_table[i].size) |
| caps->input_size = cap_table[i].size; |
| |
| break; |
| } |
| |
| if (!strcmp(str, "public-oob")) |
| caps->pub_type = 1; |
| else if (!strcmp(str, "static-oob")) |
| caps->static_type = 1; |
| } |
| |
| return true; |
| } |
| |
| static bool parse_oob_info(struct mesh_agent_prov_caps *caps, |
| struct l_dbus_message_iter *property) |
| { |
| struct l_dbus_message_iter iter_oob; |
| uint32_t i; |
| const char *str; |
| |
| if (!l_dbus_message_iter_get_variant(property, "as", &iter_oob)) |
| return false; |
| |
| while (l_dbus_message_iter_next_entry(&iter_oob, &str)) { |
| for (i = 0; i < L_ARRAY_SIZE(oob_table); i++) { |
| if (strcmp(str, oob_table[i].oob)) |
| continue; |
| caps->oob_info |= oob_table[i].mask; |
| } |
| } |
| |
| return true; |
| } |
| |
| static bool parse_properties(struct mesh_agent *agent, |
| struct l_dbus_message_iter *properties) |
| { |
| const char *key, *uri_string; |
| struct l_dbus_message_iter variant; |
| |
| memset(&agent->caps, 0, sizeof(agent->caps)); |
| |
| while (l_dbus_message_iter_next_entry(properties, &key, &variant)) { |
| if (!strcmp(key, "Capabilities")) { |
| if (!parse_prov_caps(&agent->caps, &variant)) |
| return false; |
| } else if (!strcmp(key, "URI")) { |
| uint8_t salt[16]; |
| |
| if (!l_dbus_message_iter_get_variant(&variant, "s", |
| &uri_string)) |
| return false; |
| |
| mesh_crypto_s1(uri_string, strlen(uri_string), salt); |
| agent->caps.uri_hash = |
| salt[0] << 24 | |
| salt[1] << 16 | |
| salt[2] << 8 | |
| salt[3] << 0; |
| agent->caps.oob_info |= OOB_INFO_URI_HASH; |
| } else if (!strcmp(key, "OutOfBandInfo")) { |
| if (!parse_oob_info(&agent->caps, &variant)) |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| static void agent_free(void *agent_data) |
| { |
| struct mesh_agent *agent = agent_data; |
| int err; |
| mesh_agent_cb_t simple_cb; |
| mesh_agent_key_cb_t key_cb; |
| mesh_agent_number_cb_t number_cb; |
| |
| err = MESH_ERROR_DOES_NOT_EXIST; |
| |
| if (agent->req && agent->req->cb) { |
| struct agent_request *req = agent->req; |
| |
| switch (req->type) { |
| case MESH_AGENT_REQUEST_PUSH: |
| case MESH_AGENT_REQUEST_TWIST: |
| case MESH_AGENT_REQUEST_IN_NUMERIC: |
| number_cb = req->cb; |
| number_cb(req->user_data, err, 0); |
| break; |
| case MESH_AGENT_REQUEST_IN_ALPHA: |
| case MESH_AGENT_REQUEST_STATIC_OOB: |
| case MESH_AGENT_REQUEST_PRIVATE_KEY: |
| case MESH_AGENT_REQUEST_PUBLIC_KEY: |
| key_cb = req->cb; |
| key_cb(req->user_data, err, NULL, 0); |
| break; |
| case MESH_AGENT_REQUEST_BLINK: |
| case MESH_AGENT_REQUEST_BEEP: |
| case MESH_AGENT_REQUEST_VIBRATE: |
| case MESH_AGENT_REQUEST_OUT_NUMERIC: |
| case MESH_AGENT_REQUEST_OUT_ALPHA: |
| case MESH_AGENT_REQUEST_CAPABILITIES: |
| simple_cb = agent->req->cb; |
| simple_cb(req->user_data, err); |
| default: |
| break; |
| } |
| |
| l_dbus_message_unref(req->msg); |
| l_free(req); |
| } |
| |
| l_free(agent->path); |
| l_free(agent->owner); |
| l_free(agent); |
| } |
| |
| void mesh_agent_remove(struct mesh_agent *agent) |
| { |
| if (!agent) |
| return; |
| |
| if (l_queue_remove(agents, agent)) |
| agent_free(agent); |
| } |
| |
| void mesh_agent_cleanup(void) |
| { |
| if (!agents) |
| return; |
| |
| l_queue_destroy(agents, agent_free); |
| agents = NULL; |
| |
| } |
| |
| void mesh_agent_init(void) |
| { |
| if (!agents) |
| agents = l_queue_new(); |
| } |
| |
| struct mesh_agent *mesh_agent_create(const char *path, const char *owner, |
| struct l_dbus_message_iter *properties) |
| { |
| struct mesh_agent *agent; |
| |
| agent = l_new(struct mesh_agent, 1); |
| agent->owner = l_strdup(owner); |
| agent->path = l_strdup(path); |
| |
| if (!parse_properties(agent, properties)) { |
| l_free(agent); |
| return NULL; |
| } |
| |
| l_queue_push_tail(agents, agent); |
| |
| return agent; |
| } |
| |
| struct mesh_agent_prov_caps *mesh_agent_get_caps(struct mesh_agent *agent) |
| { |
| if (!agent || !l_queue_find(agents, simple_match, agent)) |
| return NULL; |
| |
| return &agent->caps; |
| } |
| |
| static struct agent_request *create_request(agent_request_type_t type, |
| void *cb, void *data) |
| { |
| struct agent_request *req; |
| |
| req = l_new(struct agent_request, 1); |
| |
| req->type = type; |
| req->cb = cb; |
| req->user_data = data; |
| |
| return req; |
| } |
| |
| static int get_reply_error(struct l_dbus_message *reply) |
| { |
| const char *name, *desc; |
| |
| if (l_dbus_message_is_error(reply)) { |
| |
| l_dbus_message_get_error(reply, &name, &desc); |
| l_error("Agent failed (%s), %s", name, desc); |
| return MESH_ERROR_FAILED; |
| } |
| |
| return MESH_ERROR_NONE; |
| } |
| |
| static void properties_reply(struct l_dbus_message *reply, void *user_data) |
| { |
| struct mesh_agent *agent = user_data; |
| struct agent_request *req; |
| mesh_agent_cb_t cb; |
| struct l_dbus_message_iter properties; |
| int err; |
| |
| if (!l_queue_find(agents, simple_match, agent) || !agent->req) |
| return; |
| |
| req = agent->req; |
| |
| err = get_reply_error(reply); |
| |
| if (err != MESH_ERROR_NONE) |
| goto done; |
| |
| if (!l_dbus_message_get_arguments(reply, "a{sv}", &properties)) { |
| err = MESH_ERROR_FAILED; |
| goto done; |
| } |
| |
| if (!parse_properties(agent, &properties)) |
| err = MESH_ERROR_FAILED; |
| |
| done: |
| if (req->cb) { |
| cb = req->cb; |
| cb(req->user_data, err); |
| } |
| |
| l_dbus_message_unref(req->msg); |
| l_free(req); |
| agent->req = NULL; |
| } |
| |
| void mesh_agent_refresh(struct mesh_agent *agent, mesh_agent_cb_t cb, |
| void *user_data) |
| { |
| struct l_dbus *dbus = dbus_get_bus(); |
| struct l_dbus_message *msg; |
| struct l_dbus_message_builder *builder; |
| |
| agent->req = create_request(MESH_AGENT_REQUEST_CAPABILITIES, (void *)cb, |
| user_data); |
| |
| msg = l_dbus_message_new_method_call(dbus, agent->owner, agent->path, |
| L_DBUS_INTERFACE_PROPERTIES, |
| "GetAll"); |
| |
| builder = l_dbus_message_builder_new(msg); |
| l_dbus_message_builder_append_basic(builder, 's', |
| MESH_PROVISION_AGENT_INTERFACE); |
| l_dbus_message_builder_finalize(builder); |
| l_dbus_message_builder_destroy(builder); |
| |
| l_dbus_send_with_reply(dbus_get_bus(), msg, properties_reply, agent, |
| NULL); |
| |
| agent->req->msg = l_dbus_message_ref(msg); |
| } |
| |
| |
| static void simple_reply(struct l_dbus_message *reply, void *user_data) |
| { |
| struct mesh_agent *agent = user_data; |
| struct agent_request *req; |
| mesh_agent_cb_t cb; |
| int err; |
| |
| if (!l_queue_find(agents, simple_match, agent) || !agent->req) |
| return; |
| |
| req = agent->req; |
| |
| err = get_reply_error(reply); |
| |
| l_dbus_message_unref(req->msg); |
| |
| if (req->cb) { |
| cb = req->cb; |
| cb(req->user_data, err); |
| } |
| |
| l_free(req); |
| agent->req = NULL; |
| } |
| |
| static void numeric_reply(struct l_dbus_message *reply, void *user_data) |
| { |
| struct mesh_agent *agent = user_data; |
| struct agent_request *req; |
| mesh_agent_number_cb_t cb; |
| uint32_t count; |
| int err; |
| |
| if (!l_queue_find(agents, simple_match, agent) || !agent->req) |
| return; |
| |
| req = agent->req; |
| |
| err = get_reply_error(reply); |
| |
| count = 0; |
| |
| if (err == MESH_ERROR_NONE) { |
| if (!l_dbus_message_get_arguments(reply, "u", &count)) { |
| l_error("Failed to retrieve numeric input"); |
| err = MESH_ERROR_FAILED; |
| } |
| } |
| |
| l_dbus_message_unref(req->msg); |
| |
| if (req->cb) { |
| cb = req->cb; |
| cb(req->user_data, err, count); |
| } |
| |
| l_free(req); |
| agent->req = NULL; |
| } |
| |
| static void key_reply(struct l_dbus_message *reply, void *user_data) |
| { |
| struct mesh_agent *agent = user_data; |
| struct agent_request *req; |
| mesh_agent_key_cb_t cb; |
| struct l_dbus_message_iter iter_array; |
| uint32_t n = 0, expected_len = 0; |
| uint8_t *buf = NULL; |
| int err; |
| |
| if (!l_queue_find(agents, simple_match, agent) || !agent->req) |
| return; |
| |
| req = agent->req; |
| |
| err = get_reply_error(reply); |
| |
| if (err != MESH_ERROR_NONE) |
| goto done; |
| |
| if (!l_dbus_message_get_arguments(reply, "ay", &iter_array)) { |
| l_error("Failed to retrieve key input"); |
| err = MESH_ERROR_FAILED; |
| goto done; |
| } |
| |
| if (!l_dbus_message_iter_get_fixed_array(&iter_array, &buf, &n)) { |
| l_error("Failed to retrieve key input"); |
| err = MESH_ERROR_FAILED; |
| goto done; |
| } |
| |
| if (req->type == MESH_AGENT_REQUEST_PRIVATE_KEY) |
| expected_len = 32; |
| else if (req->type == MESH_AGENT_REQUEST_PUBLIC_KEY) |
| expected_len = 64; |
| else |
| expected_len = 16; |
| |
| if (n != expected_len) { |
| l_error("Bad response length: %u (need %u)", n, expected_len); |
| err = MESH_ERROR_FAILED; |
| n = 0; |
| } |
| |
| done: |
| if (req->cb) { |
| cb = req->cb; |
| cb(req->user_data, err, buf, n); |
| } |
| |
| l_dbus_message_unref(req->msg); |
| |
| l_free(req); |
| agent->req = NULL; |
| } |
| |
| static int output_request(struct mesh_agent *agent, const char *action, |
| agent_request_type_t type, uint32_t cnt, |
| void *cb, void *user_data) |
| { |
| struct l_dbus *dbus = dbus_get_bus(); |
| struct l_dbus_message *msg; |
| struct l_dbus_message_builder *builder; |
| |
| if (!l_queue_find(agents, simple_match, agent)) |
| return MESH_ERROR_DOES_NOT_EXIST; |
| |
| if (agent->req) |
| return MESH_ERROR_BUSY; |
| |
| agent->req = create_request(type, cb, user_data); |
| msg = l_dbus_message_new_method_call(dbus, agent->owner, agent->path, |
| MESH_PROVISION_AGENT_INTERFACE, |
| "DisplayNumeric"); |
| |
| builder = l_dbus_message_builder_new(msg); |
| l_dbus_message_builder_append_basic(builder, 's', action); |
| l_dbus_message_builder_append_basic(builder, 'u', &cnt); |
| l_dbus_message_builder_finalize(builder); |
| l_dbus_message_builder_destroy(builder); |
| |
| l_debug("Send DisplayNumeric request to %s %s", |
| agent->owner, agent->path); |
| |
| l_dbus_send_with_reply(dbus_get_bus(), msg, simple_reply, agent, |
| NULL); |
| |
| agent->req->msg = l_dbus_message_ref(msg); |
| |
| return MESH_ERROR_NONE; |
| } |
| |
| static int prompt_input(struct mesh_agent *agent, const char *action, |
| agent_request_type_t type, bool numeric, |
| void *cb, void *user_data) |
| { |
| struct l_dbus *dbus = dbus_get_bus(); |
| struct l_dbus_message *msg; |
| struct l_dbus_message_builder *builder; |
| const char *method_name; |
| l_dbus_message_func_t reply_cb; |
| |
| if (!l_queue_find(agents, simple_match, agent)) |
| return MESH_ERROR_DOES_NOT_EXIST; |
| |
| if (agent->req) |
| return MESH_ERROR_BUSY; |
| |
| agent->req = create_request(type, cb, user_data); |
| |
| method_name = numeric ? "PromptNumeric" : "PromptStatic"; |
| |
| msg = l_dbus_message_new_method_call(dbus, agent->owner, |
| agent->path, |
| MESH_PROVISION_AGENT_INTERFACE, |
| method_name); |
| |
| builder = l_dbus_message_builder_new(msg); |
| l_dbus_message_builder_append_basic(builder, 's', action); |
| l_dbus_message_builder_finalize(builder); |
| l_dbus_message_builder_destroy(builder); |
| |
| l_debug("Send \"%s\" input request to %s %s", action, |
| agent->owner, agent->path); |
| |
| reply_cb = numeric ? numeric_reply : key_reply; |
| |
| l_dbus_send_with_reply(dbus_get_bus(), msg, reply_cb, agent, NULL); |
| |
| agent->req->msg = l_dbus_message_ref(msg); |
| |
| return MESH_ERROR_NONE; |
| } |
| |
| static int request_key(struct mesh_agent *agent, |
| agent_request_type_t type, |
| void *cb, void *user_data) |
| { |
| struct l_dbus *dbus = dbus_get_bus(); |
| struct l_dbus_message *msg; |
| const char *method_name; |
| |
| if (!l_queue_find(agents, simple_match, agent)) |
| return MESH_ERROR_DOES_NOT_EXIST; |
| |
| if (agent->req) |
| return MESH_ERROR_BUSY; |
| |
| agent->req = create_request(type, cb, user_data); |
| |
| method_name = (type == MESH_AGENT_REQUEST_PRIVATE_KEY) ? |
| "PrivateKey" : "PublicKey"; |
| |
| msg = l_dbus_message_new_method_call(dbus, agent->owner, |
| agent->path, |
| MESH_PROVISION_AGENT_INTERFACE, |
| method_name); |
| |
| l_dbus_message_set_arguments(msg, ""); |
| |
| l_debug("Send key request to %s %s", agent->owner, agent->path); |
| |
| l_dbus_send_with_reply(dbus_get_bus(), msg, key_reply, agent, NULL); |
| |
| agent->req->msg = l_dbus_message_ref(msg); |
| |
| return MESH_ERROR_NONE; |
| } |
| |
| int mesh_agent_display_string(struct mesh_agent *agent, const char *str, |
| mesh_agent_cb_t cb, void *user_data) |
| { |
| struct l_dbus *dbus = dbus_get_bus(); |
| struct l_dbus_message *msg; |
| struct l_dbus_message_builder *builder; |
| |
| if (!l_queue_find(agents, simple_match, agent)) |
| return MESH_ERROR_DOES_NOT_EXIST; |
| |
| if (agent->req) |
| return MESH_ERROR_BUSY; |
| |
| agent->req = create_request(MESH_AGENT_REQUEST_OUT_ALPHA, |
| cb, user_data); |
| msg = l_dbus_message_new_method_call(dbus, agent->owner, agent->path, |
| MESH_PROVISION_AGENT_INTERFACE, |
| "DisplayString"); |
| |
| builder = l_dbus_message_builder_new(msg); |
| l_dbus_message_builder_append_basic(builder, 's', str); |
| l_dbus_message_builder_finalize(builder); |
| l_dbus_message_builder_destroy(builder); |
| |
| l_debug("Send DisplayString request to %s %s", |
| agent->owner, agent->path); |
| |
| l_dbus_send_with_reply(dbus_get_bus(), msg, simple_reply, agent, |
| NULL); |
| |
| agent->req->msg = l_dbus_message_ref(msg); |
| |
| return MESH_ERROR_NONE; |
| |
| } |
| |
| int mesh_agent_display_number(struct mesh_agent *agent, bool initiator, |
| uint8_t action, uint32_t count, |
| mesh_agent_cb_t cb, void *user_data) |
| { |
| const char *str_type; |
| agent_request_type_t type; |
| |
| type = action; |
| |
| if (initiator) |
| type = action + MESH_AGENT_REQUEST_PUSH; |
| |
| if (type >= L_ARRAY_SIZE(cap_table)) |
| return MESH_ERROR_INVALID_ARGS; |
| |
| str_type = cap_table[type].action; |
| |
| return output_request(agent, str_type, type, count, cb, user_data); |
| } |
| |
| int mesh_agent_prompt_number(struct mesh_agent *agent, bool initiator, |
| uint8_t action, |
| mesh_agent_number_cb_t cb, |
| void *user_data) |
| { |
| const char *str_type; |
| agent_request_type_t type; |
| |
| type = action; |
| |
| if (!initiator) |
| type = action + MESH_AGENT_REQUEST_PUSH; |
| |
| if (type >= L_ARRAY_SIZE(cap_table)) |
| return MESH_ERROR_INVALID_ARGS; |
| |
| str_type = cap_table[type].action; |
| |
| return prompt_input(agent, str_type, type, true, cb, user_data); |
| } |
| |
| int mesh_agent_prompt_alpha(struct mesh_agent *agent, bool initiator, |
| mesh_agent_key_cb_t cb, void *user_data) |
| { |
| if (initiator) |
| return prompt_input(agent, |
| cap_table[MESH_AGENT_REQUEST_OUT_ALPHA].action, |
| MESH_AGENT_REQUEST_OUT_ALPHA, false, cb, |
| user_data); |
| else |
| return prompt_input(agent, |
| cap_table[MESH_AGENT_REQUEST_IN_ALPHA].action, |
| MESH_AGENT_REQUEST_IN_ALPHA, false, cb, |
| user_data); |
| } |
| |
| int mesh_agent_request_static(struct mesh_agent *agent, mesh_agent_key_cb_t cb, |
| void *user_data) |
| { |
| return prompt_input(agent, "static-oob", MESH_AGENT_REQUEST_STATIC_OOB, |
| false, cb, user_data); |
| } |
| |
| int mesh_agent_request_private_key(struct mesh_agent *agent, |
| mesh_agent_key_cb_t cb, void *user_data) |
| { |
| return request_key(agent, MESH_AGENT_REQUEST_PRIVATE_KEY, cb, |
| user_data); |
| |
| } |
| |
| int mesh_agent_request_public_key(struct mesh_agent *agent, |
| mesh_agent_key_cb_t cb, void *user_data) |
| { |
| return request_key(agent, MESH_AGENT_REQUEST_PUBLIC_KEY, cb, |
| user_data); |
| } |
| |
| void mesh_agent_cancel(struct mesh_agent *agent) |
| { |
| struct l_dbus *dbus = dbus_get_bus(); |
| struct l_dbus_message *msg; |
| |
| if (!l_queue_find(agents, simple_match, agent)) |
| return; |
| |
| msg = l_dbus_message_new_method_call(dbus, agent->owner, agent->path, |
| MESH_PROVISION_AGENT_INTERFACE, |
| "Cancel"); |
| |
| l_dbus_message_set_arguments(msg, ""); |
| |
| l_dbus_send(dbus, msg); |
| } |