You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
365 lines
10 KiB
C
365 lines
10 KiB
C
/*
|
|
Copyright (c) 2021 Roger Light <roger@atchoo.org>
|
|
|
|
All rights reserved. This program and the accompanying materials
|
|
are made available under the terms of the Eclipse Public License 2.0
|
|
and Eclipse Distribution License v1.0 which accompany this distribution.
|
|
|
|
The Eclipse Public License is available at
|
|
https://www.eclipse.org/legal/epl-2.0/
|
|
and the Eclipse Distribution License is available at
|
|
http://www.eclipse.org/org/documents/edl-v10.php.
|
|
|
|
SPDX-License-Identifier: EPL-2.0 OR EDL-1.0
|
|
|
|
Contributors:
|
|
Roger Light - initial implementation and documentation.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#if defined(WITH_WEBSOCKETS) && WITH_WEBSOCKETS == WS_IS_BUILTIN
|
|
#ifndef WITH_TLS
|
|
# error "Builtin websockets support requires WITH_TLS=yes and openssl to be available"
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
|
|
#include "base64_mosq.h"
|
|
#include "mosquitto_internal.h"
|
|
#include "memory_mosq.h"
|
|
#include "mqtt_protocol.h"
|
|
#include "net_mosq.h"
|
|
#include "packet_mosq.h"
|
|
#include "util_mosq.h"
|
|
|
|
|
|
void ws__context_init(struct mosquitto *mosq)
|
|
{
|
|
mosq->transport = mosq_t_ws;
|
|
mosq->state = mosq_cs_new;
|
|
}
|
|
|
|
|
|
void ws__prepare_packet(struct mosquitto *mosq, struct mosquitto__packet *packet)
|
|
{
|
|
uint8_t opcode;
|
|
uint32_t masking_offset = mosq->wsd.is_client?4:0;
|
|
|
|
packet->next = NULL;
|
|
|
|
if(mosq->wsd.opcode == UINT8_MAX){
|
|
opcode = WS_BINARY;
|
|
}else if(mosq->wsd.opcode == WS_PING){
|
|
opcode = WS_PONG;
|
|
}else{
|
|
opcode = mosq->wsd.opcode;
|
|
}
|
|
if(packet->packet_length - WS_PACKET_OFFSET < 126){
|
|
if(mosq->wsd.is_client){
|
|
packet->payload[WS_PACKET_OFFSET-masking_offset-1] = (uint8_t)(packet->packet_length-WS_PACKET_OFFSET) | 0x80;
|
|
}else{
|
|
packet->payload[WS_PACKET_OFFSET-masking_offset-1] = (uint8_t)(packet->packet_length-WS_PACKET_OFFSET);
|
|
}
|
|
packet->payload[WS_PACKET_OFFSET - masking_offset-2] = 0x80 | opcode;
|
|
packet->pos = WS_PACKET_OFFSET - masking_offset - 2;
|
|
packet->to_process = packet->packet_length - WS_PACKET_OFFSET + masking_offset + 2;
|
|
}else if(packet->packet_length - WS_PACKET_OFFSET < 65536){
|
|
uint16_t plen = (uint16_t )(packet->packet_length - WS_PACKET_OFFSET);
|
|
|
|
packet->payload[WS_PACKET_OFFSET-masking_offset-1] = MOSQ_LSB(plen);
|
|
packet->payload[WS_PACKET_OFFSET-masking_offset-2] = MOSQ_MSB(plen);;
|
|
if(mosq->wsd.is_client){
|
|
packet->payload[WS_PACKET_OFFSET-masking_offset-3] = 126 | 0x80;
|
|
}else{
|
|
packet->payload[WS_PACKET_OFFSET-masking_offset-3] = 126;
|
|
}
|
|
packet->payload[WS_PACKET_OFFSET-masking_offset-4] = 0x80 | opcode;
|
|
packet->pos = WS_PACKET_OFFSET-masking_offset - 4;
|
|
packet->to_process = packet->packet_length - WS_PACKET_OFFSET + masking_offset + 4;
|
|
}else{
|
|
uint64_t plen = packet->packet_length - WS_PACKET_OFFSET;
|
|
|
|
packet->payload[WS_PACKET_OFFSET-masking_offset-1] = (uint8_t)((plen & 0x00000000000000FF) >> 0);
|
|
packet->payload[WS_PACKET_OFFSET-masking_offset-2] = (uint8_t)((plen & 0x000000000000FF00) >> 8);
|
|
packet->payload[WS_PACKET_OFFSET-masking_offset-3] = (uint8_t)((plen & 0x0000000000FF0000) >> WS_PACKET_OFFSET);
|
|
packet->payload[WS_PACKET_OFFSET-masking_offset-4] = (uint8_t)((plen & 0x00000000FF000000) >> 24);
|
|
packet->payload[WS_PACKET_OFFSET-masking_offset-5] = (uint8_t)((plen & 0x000000FF00000000) >> 32);
|
|
packet->payload[WS_PACKET_OFFSET-masking_offset-6] = (uint8_t)((plen & 0x0000FF0000000000) >> 40);
|
|
packet->payload[WS_PACKET_OFFSET-masking_offset-7] = (uint8_t)((plen & 0x00FF000000000000) >> 48);
|
|
packet->payload[WS_PACKET_OFFSET-masking_offset-8] = (uint8_t)((plen & 0xFF00000000000000) >> 56);
|
|
if(mosq->wsd.is_client){
|
|
packet->payload[WS_PACKET_OFFSET-masking_offset-9] = 127 | 0x80;
|
|
}else{
|
|
packet->payload[WS_PACKET_OFFSET-masking_offset-9] = 127;
|
|
}
|
|
packet->payload[WS_PACKET_OFFSET-masking_offset-10] = 0x80 | opcode;
|
|
packet->pos = WS_PACKET_OFFSET-masking_offset - 10;
|
|
packet->to_process = packet->packet_length - WS_PACKET_OFFSET + masking_offset + 10;
|
|
}
|
|
if(mosq->wsd.is_client){
|
|
util__random_bytes(&packet->payload[WS_PACKET_OFFSET-sizeof(uint32_t)], sizeof(uint32_t));
|
|
for(uint32_t i=0; i<packet->packet_length - WS_PACKET_OFFSET; i++){
|
|
packet->payload[WS_PACKET_OFFSET + i] ^= packet->payload[WS_PACKET_OFFSET-sizeof(uint32_t)+(i%4)];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static ssize_t read_ws_opcode(struct mosquitto *mosq)
|
|
{
|
|
ssize_t len;
|
|
uint8_t opcode;
|
|
uint8_t fin;
|
|
uint8_t hbuf;
|
|
|
|
mosq->wsd.mask_bytes = 4;
|
|
mosq->wsd.pos = 0;
|
|
mosq->wsd.mask = UINT8_MAX;
|
|
mosq->wsd.payloadlen_bytes = UINT8_MAX;
|
|
|
|
len = net__read(mosq, &hbuf, 1);
|
|
if(len <= 0) return len;
|
|
|
|
if((hbuf & 0x70) != 0x00){
|
|
mosq->wsd.disconnect_reason = 0xEA;
|
|
errno = EPROTO;
|
|
return -1;
|
|
}
|
|
opcode = (hbuf & 0x0F);
|
|
fin = (hbuf & 0x80);
|
|
switch(opcode){
|
|
case WS_CONTINUATION:
|
|
case WS_BINARY:
|
|
case WS_PING:
|
|
case WS_PONG:
|
|
case WS_CLOSE:
|
|
mosq->wsd.opcode = opcode;
|
|
break;
|
|
|
|
case WS_TEXT:
|
|
mosq->wsd.disconnect_reason = 0xEB;
|
|
errno = EPROTO;
|
|
return -1;
|
|
|
|
default:
|
|
mosq->wsd.disconnect_reason = 0xEA;
|
|
errno = EPROTO;
|
|
return -1;
|
|
break;
|
|
}
|
|
if((opcode & 0x08) && fin == 0){
|
|
/* Control packets may not be fragmented */
|
|
mosq->wsd.disconnect_reason = 0xEA;
|
|
errno = EPROTO;
|
|
return -1;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
static ssize_t read_ws_payloadlen_short(struct mosquitto *mosq)
|
|
{
|
|
ssize_t len;
|
|
uint8_t hbuf;
|
|
uint8_t plen;
|
|
|
|
len = net__read(mosq, &hbuf, 1);
|
|
if(len <= 0) return len;
|
|
|
|
mosq->wsd.mask = (hbuf & 0x80) >> 7;
|
|
plen = hbuf & 0x7F;
|
|
|
|
if(plen == 126){
|
|
mosq->wsd.payloadlen_bytes = 2;
|
|
mosq->wsd.payloadlen = 0;
|
|
}else if(plen == 127){
|
|
mosq->wsd.payloadlen_bytes = 8;
|
|
mosq->wsd.payloadlen = 0;
|
|
}else{
|
|
mosq->wsd.payloadlen_bytes = 0;
|
|
mosq->wsd.payloadlen = plen;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
static ssize_t read_ws_payloadlen_extended(struct mosquitto *mosq)
|
|
{
|
|
uint8_t hbuf[8];
|
|
ssize_t len;
|
|
|
|
len = net__read(mosq, hbuf, mosq->wsd.payloadlen_bytes);
|
|
if(len <= 0) return len;
|
|
for(ssize_t i=0; i<len; i++){
|
|
mosq->wsd.payloadlen = (mosq->wsd.payloadlen << 8) + hbuf[i];
|
|
}
|
|
mosq->wsd.payloadlen_bytes -= (uint8_t)len;
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
static ssize_t read_ws_mask(struct mosquitto *mosq)
|
|
{
|
|
ssize_t len;
|
|
|
|
len = net__read(mosq, &mosq->wsd.maskingkey[4-mosq->wsd.mask_bytes], mosq->wsd.mask_bytes);
|
|
if(len <= 0) return len;
|
|
mosq->wsd.mask_bytes -= (uint8_t)len;
|
|
if(mosq->wsd.mask_bytes > 0){
|
|
errno = EAGAIN;
|
|
return -1;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
ssize_t net__read_ws(struct mosquitto *mosq, void *buf, size_t count)
|
|
{
|
|
ssize_t len = 0;
|
|
|
|
if(mosq->wsd.payloadlen == 0){
|
|
if(mosq->wsd.opcode == UINT8_MAX){
|
|
len = read_ws_opcode(mosq);
|
|
if(len <= 0) return len;
|
|
}
|
|
|
|
if(mosq->wsd.mask == UINT8_MAX){
|
|
len = read_ws_payloadlen_short(mosq);
|
|
if(len <= 0) return len;
|
|
}
|
|
|
|
if(mosq->wsd.payloadlen_bytes > 0){
|
|
len = read_ws_payloadlen_extended(mosq);
|
|
if(len <= 0) return len;
|
|
}
|
|
|
|
if(mosq->wsd.mask == 1 && mosq->wsd.mask_bytes > 0){
|
|
len = read_ws_mask(mosq);
|
|
if(len <= 0) return len;
|
|
}
|
|
|
|
if(mosq->wsd.opcode == WS_CLOSE && mosq->wsd.payloadlen == 1){
|
|
mosq->wsd.disconnect_reason = 0xEA;
|
|
errno = EPROTO;
|
|
return -1;
|
|
}else if(mosq->wsd.payloadlen > 125 && mosq->wsd.opcode != WS_BINARY && mosq->wsd.opcode != WS_CONTINUATION){
|
|
mosq->wsd.disconnect_reason = 0xEA;
|
|
errno = EPROTO;
|
|
return -1;
|
|
}
|
|
|
|
if(mosq->wsd.payloadlen > MQTT_MAX_PAYLOAD){
|
|
errno = EPROTO;
|
|
return -1;
|
|
}
|
|
|
|
#ifndef WS_TESTING
|
|
if(mosq->wsd.opcode == WS_PING || (mosq->wsd.opcode == WS_CLOSE && mosq->wsd.payloadlen >= 2))
|
|
/* Always allocate payload for testing case, otherwise just for pings */
|
|
#endif
|
|
{
|
|
mosq->wsd.out_packet = mosquitto__calloc(1, sizeof(struct mosquitto__packet) + WS_PACKET_OFFSET + mosq->wsd.payloadlen + 1);
|
|
if(mosq->wsd.out_packet == NULL){
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
mosq->wsd.out_packet->packet_length = (uint32_t)mosq->wsd.payloadlen + WS_PACKET_OFFSET;
|
|
}
|
|
}
|
|
|
|
if(mosq->wsd.out_packet){
|
|
/* This means we are either dealing with protocol level messages (and
|
|
* hence won't be returning MQTT data to the context), or we are
|
|
* testing and should be echoing data back to the client.
|
|
* So ignore what data is being asked for, and try and read the whole
|
|
* lot at once. */
|
|
count = mosq->wsd.payloadlen - (uint64_t)mosq->wsd.pos;
|
|
buf = &mosq->wsd.out_packet->payload[WS_PACKET_OFFSET + mosq->wsd.pos];
|
|
}
|
|
|
|
if(mosq->wsd.payloadlen > 0){
|
|
if(count > mosq->wsd.payloadlen - (uint64_t)mosq->wsd.pos){
|
|
count = mosq->wsd.payloadlen - (uint64_t)mosq->wsd.pos;
|
|
}
|
|
len = net__read(mosq, buf, count);
|
|
if(len > 0){
|
|
for(ssize_t i=0; i<len; i++){
|
|
((uint8_t *)buf)[i] ^= mosq->wsd.maskingkey[(i+mosq->wsd.pos)%4];
|
|
}
|
|
mosq->wsd.pos += len;
|
|
}
|
|
}
|
|
|
|
if(mosq->wsd.pos == (ssize_t)mosq->wsd.payloadlen){
|
|
if(mosq->wsd.opcode == WS_CLOSE){
|
|
mosquitto__FREE(mosq->wsd.out_packet);
|
|
|
|
/* Testing or PING - so we haven't read any data for the application yet. */
|
|
len = -1;
|
|
errno = EAGAIN;
|
|
}else if(mosq->wsd.opcode == WS_PONG){
|
|
mosquitto__FREE(mosq->wsd.out_packet);
|
|
/* Testing or PING - so we haven't read any data for the application yet. */
|
|
len = -1;
|
|
errno = EAGAIN;
|
|
}else if(mosq->wsd.out_packet){
|
|
packet__queue(mosq, mosq->wsd.out_packet);
|
|
mosq->wsd.out_packet = NULL;
|
|
|
|
/* Testing or PING - so we haven't read any data for the application yet.
|
|
* Simulate that situation. This has to be done *after* the call to
|
|
* packet__queue. */
|
|
len = -1;
|
|
errno = EAGAIN;
|
|
}
|
|
mosq->wsd.payloadlen = 0;
|
|
mosq->wsd.opcode = UINT8_MAX;
|
|
mosq->wsd.mask = UINT8_MAX;
|
|
}else if(mosq->wsd.out_packet){
|
|
/* Testing or PING - so we haven't read any data for the application yet.
|
|
* Simulate that situation.*/
|
|
len = -1;
|
|
errno = EAGAIN;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
int ws__create_accept_key(const char *client_key, size_t client_key_len, char **encoded)
|
|
{
|
|
const EVP_MD *digest;
|
|
EVP_MD_CTX *evp = NULL;
|
|
uint8_t accept_key_hash[EVP_MAX_MD_SIZE];
|
|
unsigned int accept_key_hash_len;
|
|
|
|
digest = EVP_get_digestbyname("sha1");
|
|
if(!digest){
|
|
return MOSQ_ERR_UNKNOWN;
|
|
}
|
|
|
|
evp = EVP_MD_CTX_new();
|
|
if(evp && EVP_DigestInit_ex(evp, digest, NULL) != 0){
|
|
if(EVP_DigestUpdate(evp, client_key, client_key_len) != 0){
|
|
if(EVP_DigestUpdate(evp, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
|
|
strlen("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")) != 0){
|
|
|
|
if(EVP_DigestFinal_ex(evp, accept_key_hash, &accept_key_hash_len) != 0){
|
|
EVP_MD_CTX_free(evp);
|
|
return base64__encode(accept_key_hash, accept_key_hash_len, encoded);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
EVP_MD_CTX_free(evp);
|
|
return MOSQ_ERR_UNKNOWN;
|
|
}
|
|
|
|
|
|
#endif
|