OVMS3-idf/components/protocomm/src/transports/protocomm_httpd.c
Piyush Shah 05852bc4aa protocomm_httpd: Restart security session if request is received on a new session
This commit fixes a bug as well as changes a behaviour.

Bugfix: During softap/httpd based provisioning, if a session was closed
midway and a new one started, it would never proceed if the http server
assigns same socket number to the new session (which happens almost always).
Now, if a session is closed, using the http callbacks, the older session
data is cleared so that a new one can be created.

Behavioural change: If a client (mobile app particularly) does not use
persistent http session i.e. all provisioning communication on the same
socket, the provisioning may fail. Earlier, since the session context was
not getting cleared, even if the client closed a session and continued
on a new one, it would go through if the socket number assigned was same
(which happens almost always).

Ideally, from a security perspective, all communication related
to secure provisioning must happen on the same socket, and so, this
change is required.
2020-05-11 13:01:13 +00:00

277 lines
8.8 KiB
C

// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_log.h>
#include <esp_err.h>
#include <esp_http_server.h>
#include <protocomm.h>
#include <protocomm_httpd.h>
#include "protocomm_priv.h"
static const char *TAG = "protocomm_httpd";
static protocomm_t *pc_httpd; /* The global protocomm instance for HTTPD */
static bool pc_ext_httpd_handle_provided = false;
static uint32_t session_id = PROTOCOMM_NO_SESSION_ID;
#define MAX_REQ_BODY_LEN 4096
static void protocomm_httpd_session_close(void *ctx)
{
if (pc_httpd->sec && pc_httpd->sec->close_transport_session) {
ESP_LOGW(TAG, "Closing session as socket %d was closed", session_id);
if (pc_httpd->sec->close_transport_session((protocomm_security_handle_t)ctx, session_id) != ESP_OK) {
ESP_LOGW(TAG, "Error closing session with ID: %d", session_id);
}
}
session_id = PROTOCOMM_NO_SESSION_ID;
}
static esp_err_t common_post_handler(httpd_req_t *req)
{
esp_err_t ret;
uint8_t *outbuf = NULL;
char *req_body = NULL;
const char *ep_name = NULL;
ssize_t outlen;
int cur_session_id = httpd_req_to_sockfd(req);
if (cur_session_id != session_id) {
ESP_LOGI(TAG, "Creating new session: %d", cur_session_id);
/* Initialize new security session */
if (session_id != PROTOCOMM_NO_SESSION_ID) {
ESP_LOGD(TAG, "Closing session with ID: %d", session_id);
/* Presently HTTP server doesn't support callback on socket closure so
* previous session can only be closed when new session is requested */
if (pc_httpd->sec && pc_httpd->sec->close_transport_session) {
ret = pc_httpd->sec->close_transport_session(pc_httpd->sec_inst, session_id);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "Error closing session with ID: %d", session_id);
}
}
session_id = PROTOCOMM_NO_SESSION_ID;
}
if (pc_httpd->sec && pc_httpd->sec->new_transport_session) {
ret = pc_httpd->sec->new_transport_session(pc_httpd->sec_inst, cur_session_id);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to launch new session with ID: %d", cur_session_id);
ret = ESP_FAIL;
goto out;
}
req->sess_ctx = pc_httpd->sec_inst;
req->free_ctx = protocomm_httpd_session_close;
}
session_id = cur_session_id;
ESP_LOGD(TAG, "New session with ID: %d", cur_session_id);
}
if (req->content_len <= 0) {
ESP_LOGE(TAG, "Content length not found");
ret = ESP_FAIL;
goto out;
} else if (req->content_len > MAX_REQ_BODY_LEN) {
ESP_LOGE(TAG, "Request content length should be less than 4kb");
ret = ESP_FAIL;
goto out;
}
req_body = (char *) malloc(req->content_len);
if (!req_body) {
ESP_LOGE(TAG, "Unable to allocate for request length %d", req->content_len);
ret = ESP_ERR_NO_MEM;
goto out;
}
size_t recv_size = 0;
while (recv_size < req->content_len) {
ret = httpd_req_recv(req, req_body + recv_size, req->content_len - recv_size);
if (ret < 0) {
ret = ESP_FAIL;
goto out;
}
recv_size += ret;
}
/* Extract the endpoint name from URI string of type "/ep_name" */
ep_name = req->uri + 1;
ret = protocomm_req_handle(pc_httpd, ep_name, session_id,
(uint8_t *)req_body, recv_size, &outbuf, &outlen);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Data handler failed");
ret = ESP_FAIL;
goto out;
}
ret = httpd_resp_send(req, (char *)outbuf, outlen);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "HTTP send failed");
ret = ESP_FAIL;
goto out;
}
ret = ESP_OK;
out:
if (req_body) {
free(req_body);
}
if (outbuf) {
free(outbuf);
}
return ret;
}
static esp_err_t protocomm_httpd_add_endpoint(const char *ep_name,
protocomm_req_handler_t req_handler,
void *priv_data)
{
if (pc_httpd == NULL) {
return ESP_ERR_INVALID_STATE;
}
ESP_LOGD(TAG, "Adding endpoint : %s", ep_name);
/* Construct URI name by prepending '/' to ep_name */
char* ep_uri = calloc(1, strlen(ep_name) + 2);
if (!ep_uri) {
ESP_LOGE(TAG, "Malloc failed for ep uri");
return ESP_ERR_NO_MEM;
}
/* Create URI handler structure */
sprintf(ep_uri, "/%s", ep_name);
httpd_uri_t config_handler = {
.uri = ep_uri,
.method = HTTP_POST,
.handler = common_post_handler,
.user_ctx = NULL
};
/* Register URI handler */
esp_err_t err;
httpd_handle_t *server = (httpd_handle_t *) pc_httpd->priv;
if ((err = httpd_register_uri_handler(*server, &config_handler)) != ESP_OK) {
ESP_LOGE(TAG, "Uri handler register failed: %s", esp_err_to_name(err));
free(ep_uri);
return ESP_FAIL;
}
free(ep_uri);
return ESP_OK;
}
static esp_err_t protocomm_httpd_remove_endpoint(const char *ep_name)
{
if (pc_httpd == NULL) {
return ESP_ERR_INVALID_STATE;
}
ESP_LOGD(TAG, "Removing endpoint : %s", ep_name);
/* Construct URI name by prepending '/' to ep_name */
char* ep_uri = calloc(1, strlen(ep_name) + 2);
if (!ep_uri) {
ESP_LOGE(TAG, "Malloc failed for ep uri");
return ESP_ERR_NO_MEM;
}
sprintf(ep_uri, "/%s", ep_name);
/* Unregister URI handler */
esp_err_t err;
httpd_handle_t *server = (httpd_handle_t *) pc_httpd->priv;
if ((err = httpd_unregister_uri_handler(*server, ep_uri, HTTP_POST)) != ESP_OK) {
ESP_LOGE(TAG, "Uri handler de-register failed: %s", esp_err_to_name(err));
free(ep_uri);
return ESP_FAIL;
}
free(ep_uri);
return ESP_OK;
}
esp_err_t protocomm_httpd_start(protocomm_t *pc, const protocomm_httpd_config_t *config)
{
if (!pc || !config) {
return ESP_ERR_INVALID_ARG;
}
if (pc_httpd) {
if (pc == pc_httpd) {
ESP_LOGE(TAG, "HTTP server already running for this protocomm instance");
return ESP_ERR_INVALID_STATE;
}
ESP_LOGE(TAG, "HTTP server started for another protocomm instance");
return ESP_ERR_NOT_SUPPORTED;
}
if (config->ext_handle_provided) {
if (config->data.handle) {
pc->priv = config->data.handle;
pc_ext_httpd_handle_provided = true;
} else {
return ESP_ERR_INVALID_ARG;
}
} else {
/* Private data will point to the HTTP server handle */
pc->priv = calloc(1, sizeof(httpd_handle_t));
if (!pc->priv) {
ESP_LOGE(TAG, "Malloc failed for HTTP server handle");
return ESP_ERR_NO_MEM;
}
/* Configure the HTTP server */
httpd_config_t server_config = HTTPD_DEFAULT_CONFIG();
server_config.server_port = config->data.config.port;
server_config.stack_size = config->data.config.stack_size;
server_config.task_priority = config->data.config.task_priority;
server_config.lru_purge_enable = true;
server_config.max_open_sockets = 1;
esp_err_t err;
if ((err = httpd_start((httpd_handle_t *)pc->priv, &server_config)) != ESP_OK) {
ESP_LOGE(TAG, "Failed to start http server: %s", esp_err_to_name(err));
free(pc->priv);
return err;
}
}
pc->add_endpoint = protocomm_httpd_add_endpoint;
pc->remove_endpoint = protocomm_httpd_remove_endpoint;
pc_httpd = pc;
session_id = PROTOCOMM_NO_SESSION_ID;
return ESP_OK;
}
esp_err_t protocomm_httpd_stop(protocomm_t *pc)
{
if ((pc != NULL) && (pc == pc_httpd)) {
if (!pc_ext_httpd_handle_provided) {
httpd_handle_t *server_handle = (httpd_handle_t *) pc_httpd->priv;
httpd_stop(*server_handle);
free(server_handle);
} else {
pc_ext_httpd_handle_provided = false;
}
pc_httpd->priv = NULL;
pc_httpd = NULL;
session_id = PROTOCOMM_NO_SESSION_ID;
return ESP_OK;
}
return ESP_ERR_INVALID_ARG;
}