This removes some tricky math to calculate lengths and offsets, ensuring that, when appending the WebSocket UUID to the handshake key, the key_accept buffer won't overflow. Coverity-ID: 183057 Signed-off-by: Leandro Pereira <leandro.pereira@intel.com>
486 lines
10 KiB
C
486 lines
10 KiB
C
/*
|
|
* Copyright (c) 2017 Intel Corporation.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#if defined(CONFIG_NET_DEBUG_WEBSOCKET)
|
|
#define SYS_LOG_DOMAIN "ws"
|
|
#define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG
|
|
#define NET_LOG_ENABLED 1
|
|
#endif
|
|
|
|
#include <zephyr.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <version.h>
|
|
|
|
#include <net/net_ip.h>
|
|
#include <net/websocket.h>
|
|
|
|
#include <base64.h>
|
|
#include <mbedtls/sha1.h>
|
|
|
|
#define BUF_ALLOC_TIMEOUT 100
|
|
|
|
#define HTTP_CRLF "\r\n"
|
|
|
|
/* From RFC 6455 chapter 4.2.2 */
|
|
#define WS_MAGIC "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
|
|
|
static void ws_mask_payload(u8_t *payload, size_t payload_len,
|
|
u32_t masking_value)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < payload_len; i++) {
|
|
payload[i] ^= masking_value >> (8 * (3 - i % 4));
|
|
}
|
|
}
|
|
|
|
void ws_mask_pkt(struct net_pkt *pkt, u32_t masking_value, u32_t *data_read)
|
|
{
|
|
struct net_buf *frag;
|
|
u16_t pos;
|
|
int i;
|
|
|
|
frag = net_frag_get_pos(pkt,
|
|
net_pkt_get_len(pkt) - net_pkt_appdatalen(pkt),
|
|
&pos);
|
|
if (!frag) {
|
|
return;
|
|
}
|
|
|
|
NET_ASSERT(net_pkt_appdata(pkt) == frag->data + pos);
|
|
|
|
while (frag) {
|
|
for (i = pos; i < frag->len; i++, (*data_read)++) {
|
|
frag->data[i] ^=
|
|
masking_value >> (8 * (3 - (*data_read) % 4));
|
|
}
|
|
|
|
pos = 0;
|
|
frag = frag->frags;
|
|
}
|
|
}
|
|
|
|
int ws_send_msg(struct http_ctx *ctx, u8_t *payload, size_t payload_len,
|
|
enum ws_opcode opcode, bool mask, bool final,
|
|
const struct sockaddr *dst,
|
|
void *user_send_data)
|
|
{
|
|
u8_t header[14], hdr_len = 2;
|
|
int ret;
|
|
|
|
if (ctx->state != HTTP_STATE_OPEN) {
|
|
return -ENOTCONN;
|
|
}
|
|
|
|
if (opcode != WS_OPCODE_DATA_TEXT && opcode != WS_OPCODE_DATA_BINARY &&
|
|
opcode != WS_OPCODE_CONTINUE && opcode != WS_OPCODE_CLOSE &&
|
|
opcode != WS_OPCODE_PING && opcode != WS_OPCODE_PONG) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
memset(header, 0, sizeof(header));
|
|
|
|
/* Is this the last packet? */
|
|
header[0] = final ? BIT(7) : 0;
|
|
|
|
/* Text, binary, ping, pong or close ? */
|
|
header[0] |= opcode;
|
|
|
|
/* Masking */
|
|
header[1] = mask ? BIT(7) : 0;
|
|
|
|
if (payload_len < 126) {
|
|
header[1] |= payload_len;
|
|
} else if (payload_len < 65536) {
|
|
header[1] |= 126;
|
|
header[2] = payload_len >> 8;
|
|
header[3] = payload_len;
|
|
hdr_len += 2;
|
|
} else {
|
|
header[1] |= 127;
|
|
header[2] = 0;
|
|
header[3] = 0;
|
|
header[4] = 0;
|
|
header[5] = 0;
|
|
header[6] = payload_len >> 24;
|
|
header[7] = payload_len >> 16;
|
|
header[8] = payload_len >> 8;
|
|
header[9] = payload_len;
|
|
hdr_len += 8;
|
|
}
|
|
|
|
/* Add masking value if needed */
|
|
if (mask) {
|
|
u32_t masking_value;
|
|
|
|
masking_value = sys_rand32_get();
|
|
|
|
header[hdr_len++] |= masking_value >> 24;
|
|
header[hdr_len++] |= masking_value >> 16;
|
|
header[hdr_len++] |= masking_value >> 8;
|
|
header[hdr_len++] |= masking_value;
|
|
|
|
ws_mask_payload(payload, payload_len, masking_value);
|
|
}
|
|
|
|
ret = http_prepare_and_send(ctx, header, hdr_len, dst, user_send_data);
|
|
if (ret < 0) {
|
|
NET_DBG("Cannot add ws header (%d)", ret);
|
|
goto quit;
|
|
}
|
|
|
|
if (payload) {
|
|
ret = http_prepare_and_send(ctx, payload, payload_len,
|
|
dst, user_send_data);
|
|
if (ret < 0) {
|
|
NET_DBG("Cannot send %zd bytes message (%d)",
|
|
payload_len, ret);
|
|
goto quit;
|
|
}
|
|
}
|
|
|
|
ret = http_send_flush(ctx, user_send_data);
|
|
|
|
quit:
|
|
return ret;
|
|
}
|
|
|
|
int ws_strip_header(struct net_pkt *pkt, bool *masked, u32_t *mask_value,
|
|
u32_t *message_length, u32_t *message_type_flag,
|
|
u32_t *header_len)
|
|
{
|
|
struct net_buf *frag;
|
|
u16_t value, pos, appdata_pos;
|
|
u8_t len; /* message length byte */
|
|
u8_t len_len; /* length of the length field in header */
|
|
|
|
frag = net_frag_read_be16(pkt->frags, 0, &pos, &value);
|
|
if (!frag && pos == 0xffff) {
|
|
return -ENOMSG;
|
|
}
|
|
|
|
if (value & 0x8000) {
|
|
*message_type_flag |= WS_FLAG_FINAL;
|
|
}
|
|
|
|
switch (value & 0x0f00) {
|
|
case 0x0100:
|
|
*message_type_flag |= WS_FLAG_TEXT;
|
|
break;
|
|
case 0x0200:
|
|
*message_type_flag |= WS_FLAG_BINARY;
|
|
break;
|
|
case 0x0800:
|
|
*message_type_flag |= WS_FLAG_CLOSE;
|
|
break;
|
|
case 0x0900:
|
|
*message_type_flag |= WS_FLAG_PING;
|
|
break;
|
|
case 0x0A00:
|
|
*message_type_flag |= WS_FLAG_PONG;
|
|
break;
|
|
}
|
|
|
|
len = value & 0x007f;
|
|
if (len < 126) {
|
|
len_len = 0;
|
|
*message_length = len;
|
|
} else if (len == 126) {
|
|
u16_t msg_len;
|
|
|
|
len_len = 2;
|
|
|
|
frag = net_frag_read_be16(frag, pos, &pos, &msg_len);
|
|
if (!frag && pos == 0xffff) {
|
|
return -ENOMSG;
|
|
}
|
|
|
|
*message_length = msg_len;
|
|
} else {
|
|
len_len = 4;
|
|
|
|
frag = net_frag_read_be32(frag, pos, &pos, message_length);
|
|
if (!frag && pos == 0xffff) {
|
|
return -ENOMSG;
|
|
}
|
|
}
|
|
|
|
if (value & 0x0080) {
|
|
*masked = true;
|
|
appdata_pos = 0;
|
|
|
|
frag = net_frag_read_be32(frag, pos, &pos, mask_value);
|
|
if (!frag && pos == 0xffff) {
|
|
return -ENOMSG;
|
|
}
|
|
} else {
|
|
*masked = false;
|
|
appdata_pos = len_len;
|
|
}
|
|
|
|
frag = net_frag_get_pos(pkt, pos + appdata_pos, &pos);
|
|
if (!frag && pos == 0xffff) {
|
|
return -ENOMSG;
|
|
}
|
|
|
|
/* Minimum websocket header is 6 bytes, header length might be
|
|
* bigger depending on length field len.
|
|
*/
|
|
*header_len = 6 + len_len;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool field_contains(const char *field, int field_len,
|
|
const char *str, int str_len)
|
|
{
|
|
bool found = false;
|
|
char c, skip;
|
|
|
|
c = *str++;
|
|
if (c == '\0') {
|
|
return false;
|
|
}
|
|
|
|
str_len--;
|
|
|
|
do {
|
|
do {
|
|
skip = *field++;
|
|
field_len--;
|
|
if (skip == '\0' || field_len == 0) {
|
|
return false;
|
|
}
|
|
} while (skip != c);
|
|
|
|
if (field_len < str_len) {
|
|
return false;
|
|
}
|
|
|
|
if (strncasecmp(field, str, str_len) == 0) {
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
} while (field_len >= str_len);
|
|
|
|
return found;
|
|
}
|
|
|
|
static bool check_ws_headers(struct http_ctx *ctx, struct http_parser *parser,
|
|
int *ws_sec_key, int *host, int *subprotocol)
|
|
{
|
|
int i, count, connection = -1;
|
|
int ws_sec_version = -1;
|
|
|
|
if (!parser->upgrade || parser->method != HTTP_GET ||
|
|
parser->http_major != 1 || parser->http_minor != 1) {
|
|
return false;
|
|
}
|
|
|
|
for (i = 0, count = 0; i < ctx->http.field_values_ctr; i++) {
|
|
if (ctx->http.field_values[i].key_len == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (strncasecmp(ctx->http.field_values[i].key,
|
|
"Sec-WebSocket-Key",
|
|
sizeof("Sec-WebSocket-Key") - 1) == 0) {
|
|
*ws_sec_key = i;
|
|
continue;
|
|
}
|
|
|
|
if (strncasecmp(ctx->http.field_values[i].key,
|
|
"Sec-WebSocket-Version",
|
|
sizeof("Sec-WebSocket-Version") - 1) == 0) {
|
|
if (strncmp(ctx->http.field_values[i].value,
|
|
"13", sizeof("13") - 1) == 0) {
|
|
ws_sec_version = i;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (strncasecmp(ctx->http.field_values[i].key,
|
|
"Connection", sizeof("Connection") - 1) == 0) {
|
|
if (field_contains(
|
|
ctx->http.field_values[i].value,
|
|
ctx->http.field_values[i].value_len,
|
|
"Upgrade", sizeof("Upgrade") - 1)) {
|
|
connection = i;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (strncasecmp(ctx->http.field_values[i].key, "Host",
|
|
sizeof("Host") - 1) == 0) {
|
|
*host = i;
|
|
continue;
|
|
}
|
|
|
|
if (strncasecmp(ctx->http.field_values[i].key,
|
|
"Sec-WebSocket-Protocol",
|
|
sizeof("Sec-WebSocket-Protocol") - 1) == 0) {
|
|
*subprotocol = i;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (connection >= 0 && *ws_sec_key >= 0 && ws_sec_version >= 0 &&
|
|
*host >= 0) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static struct net_pkt *prepare_reply(struct http_ctx *ctx,
|
|
int ws_sec_key, int host, int subprotocol)
|
|
{
|
|
static const char basic_reply_headers[] =
|
|
"HTTP/1.1 101 OK\r\n"
|
|
"Upgrade: websocket\r\n"
|
|
"Connection: Upgrade\r\n"
|
|
"Sec-WebSocket-Accept: ";
|
|
char key_accept[32 + sizeof(WS_MAGIC)];
|
|
char accept[20];
|
|
struct net_pkt *pkt;
|
|
char tmp[64];
|
|
int ret;
|
|
size_t key_len;
|
|
size_t olen;
|
|
|
|
pkt = net_app_get_net_pkt_with_dst(&ctx->app_ctx,
|
|
ctx->http.parser.addr,
|
|
ctx->timeout);
|
|
if (!pkt) {
|
|
return NULL;
|
|
}
|
|
|
|
if (!net_pkt_append_all(pkt, sizeof(basic_reply_headers) - 1,
|
|
(u8_t *)basic_reply_headers, ctx->timeout)) {
|
|
goto fail;
|
|
}
|
|
|
|
key_len = min(sizeof(key_accept) - 1,
|
|
ctx->http.field_values[ws_sec_key].value_len);
|
|
strncpy(key_accept, ctx->http.field_values[ws_sec_key].value,
|
|
key_len);
|
|
|
|
olen = min(sizeof(key_accept) - 1 - key_len, sizeof(WS_MAGIC) - 1);
|
|
strncpy(key_accept + key_len, WS_MAGIC, olen);
|
|
|
|
mbedtls_sha1(key_accept, olen + key_len, accept);
|
|
|
|
ret = base64_encode(tmp, sizeof(tmp) - 1, &olen, accept,
|
|
sizeof(accept));
|
|
if (ret) {
|
|
if (ret == -ENOMEM) {
|
|
NET_DBG("[%p] Too short buffer olen %zd", ctx, olen);
|
|
}
|
|
|
|
goto fail;
|
|
}
|
|
if (!net_pkt_append_all(pkt, olen, (u8_t *)tmp, ctx->timeout)) {
|
|
goto fail;
|
|
}
|
|
|
|
ret = snprintk(tmp, sizeof(tmp), "User-Agent: %s\r\n\r\n",
|
|
ZEPHYR_USER_AGENT);
|
|
if (ret < 0 || ret >= sizeof(tmp)) {
|
|
goto fail;
|
|
}
|
|
if (!net_pkt_append_all(pkt, ret, (u8_t *)tmp, ctx->timeout)) {
|
|
goto fail;
|
|
}
|
|
|
|
return pkt;
|
|
|
|
fail:
|
|
net_pkt_unref(pkt);
|
|
return NULL;
|
|
}
|
|
|
|
int ws_headers_complete(struct http_parser *parser)
|
|
{
|
|
struct http_ctx *ctx = parser->data;
|
|
int ws_sec_key = -1, host = -1, subprotocol = -1;
|
|
|
|
if (check_ws_headers(ctx, parser, &ws_sec_key, &host,
|
|
&subprotocol)) {
|
|
struct net_pkt *pkt;
|
|
struct http_root_url *url;
|
|
int ret;
|
|
|
|
url = http_url_find(ctx, HTTP_URL_WEBSOCKET);
|
|
if (!url) {
|
|
url = http_url_find(ctx, HTTP_URL_STANDARD);
|
|
if (url) {
|
|
/* Normal HTTP URL was found */
|
|
return 0;
|
|
}
|
|
|
|
/* If there is no URL to serve this websocket
|
|
* request, then just bail out.
|
|
*/
|
|
if (!ctx->http.urls) {
|
|
NET_DBG("[%p] No URL handlers found", ctx);
|
|
return 0;
|
|
}
|
|
|
|
url = &ctx->http.urls->default_url;
|
|
if (url && url->is_used &&
|
|
ctx->http.urls->default_cb) {
|
|
ret = ctx->http.urls->default_cb(ctx,
|
|
WS_CONNECTION,
|
|
ctx->http.parser.addr);
|
|
if (ret == HTTP_VERDICT_ACCEPT) {
|
|
goto accept;
|
|
}
|
|
}
|
|
|
|
if (url->flags == HTTP_URL_WEBSOCKET) {
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
if (url->flags != HTTP_URL_WEBSOCKET) {
|
|
return 0;
|
|
}
|
|
|
|
accept:
|
|
NET_DBG("[%p] ws header %d fields found", ctx,
|
|
ctx->http.field_values_ctr + 1);
|
|
|
|
pkt = prepare_reply(ctx, ws_sec_key, host, subprotocol);
|
|
if (!pkt) {
|
|
goto fail;
|
|
}
|
|
|
|
net_pkt_set_appdatalen(pkt, net_buf_frags_len(pkt->frags));
|
|
|
|
ret = net_app_send_pkt(&ctx->app_ctx, pkt, NULL, 0, 0,
|
|
INT_TO_POINTER(K_FOREVER));
|
|
if (ret) {
|
|
goto fail;
|
|
}
|
|
|
|
http_change_state(ctx, HTTP_STATE_HEADER_RECEIVED);
|
|
|
|
/* We do not expect any HTTP data after this */
|
|
return 2;
|
|
|
|
fail:
|
|
http_change_state(ctx, HTTP_STATE_CLOSED);
|
|
}
|
|
|
|
return 0;
|
|
}
|