kopia lustrzana https://github.com/meshtastic/firmware
Native Webserver (#3343)
* Added WebServer/WebServices for Native Linux Meshtastic and web gui * Fix bug in login functionality * Added customized config of portdunio.ini with LovyannGFX from marelab repro * Compile Problem resolved with developer version of LovyanGFX.git * Compile against dev version * Fixes to fit into main branch * Update variant.h, main.cpp, .gitignore, WebServer.cpp, esp32s2.ini, WebServer.h, ContentHandler.cpp, rp2040.ini, nrf52.ini, ContentHelper.cpp, Dockerfile, ContentHandler.h, esp32.ini, stm32wl5e.ini * Added linux pi std /usr/include dir * Adding /usr/innclude for Linux compile against native libs that are not hadled by platformio * Review log level changes & translation * Update Dockerfile * Fix Typo & VFS ref. Part1 * Fix Typo & VFS ref. * Dev Version for ulfius web lib * Update platformio.ini * Free VFS path string * Remove unintended changes * More unintentional changes * Make the HTTP server optional on native * Tune-up for Native web defaults * Don't modify build system yet * Remove more unneeded changes --------- Co-authored-by: marc hammermann <marchammermann@googlemail.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>pull/3344/head
rodzic
9d37a8d17f
commit
e174328de3
|
@ -31,3 +31,5 @@ venv/
|
||||||
release/
|
release/
|
||||||
.vscode/extensions.json
|
.vscode/extensions.json
|
||||||
/compile_commands.json
|
/compile_commands.json
|
||||||
|
src/mesh/raspihttp/certificate.pem
|
||||||
|
src/mesh/raspihttp/private_key.pem
|
|
@ -4,7 +4,7 @@ extends = arduino_base
|
||||||
platform = platformio/espressif32@6.3.2 # This is a temporary fix to the S3-based devices bluetooth issues until we can determine what within ESP-IDF changed and can develop a suitable patch.
|
platform = platformio/espressif32@6.3.2 # This is a temporary fix to the S3-based devices bluetooth issues until we can determine what within ESP-IDF changed and can develop a suitable patch.
|
||||||
|
|
||||||
build_src_filter =
|
build_src_filter =
|
||||||
${arduino_base.build_src_filter} -<platform/nrf52/> -<platform/stm32wl> -<platform/rp2040> -<mesh/eth/>
|
${arduino_base.build_src_filter} -<platform/nrf52/> -<platform/stm32wl> -<platform/rp2040> -<mesh/eth/> -<mesh/raspihttp>
|
||||||
|
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
debug_init_break = tbreak setup
|
debug_init_break = tbreak setup
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
extends = esp32_base
|
extends = esp32_base
|
||||||
|
|
||||||
build_src_filter =
|
build_src_filter =
|
||||||
${esp32_base.build_src_filter} -<nimble/>
|
${esp32_base.build_src_filter} -<nimble/> -<mesh/raspihttp>
|
||||||
|
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
|
|
||||||
|
@ -12,5 +12,4 @@ build_flags =
|
||||||
|
|
||||||
lib_ignore =
|
lib_ignore =
|
||||||
${esp32_base.lib_ignore}
|
${esp32_base.lib_ignore}
|
||||||
NimBLE-Arduino
|
NimBLE-Arduino
|
||||||
|
|
|
@ -11,7 +11,7 @@ build_flags =
|
||||||
-Isrc/platform/nrf52
|
-Isrc/platform/nrf52
|
||||||
|
|
||||||
build_src_filter =
|
build_src_filter =
|
||||||
${arduino_base.build_src_filter} -<platform/esp32/> -<platform/stm32wl> -<nimble/> -<mesh/wifi/> -<mesh/api/> -<mesh/http/> -<modules/esp32> -<platform/rp2040> -<mesh/eth/>
|
${arduino_base.build_src_filter} -<platform/esp32/> -<platform/stm32wl> -<nimble/> -<mesh/wifi/> -<mesh/api/> -<mesh/http/> -<modules/esp32> -<platform/rp2040> -<mesh/eth/> -<mesh/raspihttp>
|
||||||
|
|
||||||
lib_deps=
|
lib_deps=
|
||||||
${arduino_base.lib_deps}
|
${arduino_base.lib_deps}
|
||||||
|
|
|
@ -12,6 +12,7 @@ build_src_filter =
|
||||||
-<platform/rp2040>
|
-<platform/rp2040>
|
||||||
-<mesh/wifi/>
|
-<mesh/wifi/>
|
||||||
-<mesh/http/>
|
-<mesh/http/>
|
||||||
|
+<mesh/raspihttp/>
|
||||||
-<mesh/eth/>
|
-<mesh/eth/>
|
||||||
-<modules/esp32>
|
-<modules/esp32>
|
||||||
-<modules/Telemetry/EnvironmentTelemetry.cpp>
|
-<modules/Telemetry/EnvironmentTelemetry.cpp>
|
||||||
|
|
|
@ -12,7 +12,7 @@ build_flags =
|
||||||
-D__PLAT_RP2040__
|
-D__PLAT_RP2040__
|
||||||
# -D _POSIX_THREADS
|
# -D _POSIX_THREADS
|
||||||
build_src_filter =
|
build_src_filter =
|
||||||
${arduino_base.build_src_filter} -<platform/esp32/> -<nimble/> -<modules/esp32> -<platform/nrf52/> -<platform/stm32wl> -<mesh/eth/> -<mesh/wifi/> -<mesh/http/>
|
${arduino_base.build_src_filter} -<platform/esp32/> -<nimble/> -<modules/esp32> -<platform/nrf52/> -<platform/stm32wl> -<mesh/eth/> -<mesh/wifi/> -<mesh/http/> -<mesh/raspihttp>
|
||||||
|
|
||||||
lib_ignore =
|
lib_ignore =
|
||||||
BluetoothOTA
|
BluetoothOTA
|
||||||
|
|
|
@ -13,7 +13,7 @@ build_flags =
|
||||||
-DVECT_TAB_OFFSET=0x08000000
|
-DVECT_TAB_OFFSET=0x08000000
|
||||||
|
|
||||||
build_src_filter =
|
build_src_filter =
|
||||||
${arduino_base.build_src_filter} -<platform/esp32/> -<nimble/> -<mesh/api/> -<mesh/wifi/> -<mesh/http/> -<modules/esp32> -<mesh/eth/> -<input> -<buzz> -<modules/Telemetry> -<platform/nrf52> -<platform/portduino> -<platform/rp2040>
|
${arduino_base.build_src_filter} -<platform/esp32/> -<nimble/> -<mesh/api/> -<mesh/wifi/> -<mesh/http/> -<modules/esp32> -<mesh/eth/> -<input> -<buzz> -<modules/Telemetry> -<platform/nrf52> -<platform/portduino> -<platform/rp2040> -<mesh/raspihttp>
|
||||||
|
|
||||||
board_upload.offset_address = 0x08000000
|
board_upload.offset_address = 0x08000000
|
||||||
upload_protocol = stlink
|
upload_protocol = stlink
|
||||||
|
|
|
@ -117,3 +117,7 @@ Input:
|
||||||
|
|
||||||
Logging:
|
Logging:
|
||||||
LogLevel: info # debug, info, warn, error
|
LogLevel: info # debug, info, warn, error
|
||||||
|
|
||||||
|
Webserver:
|
||||||
|
# Port: 443 # Port for Webserver & Webservices
|
||||||
|
# RootPath: /usr/share/doc/meshtasticd/web # Root Dir of WebServer
|
||||||
|
|
|
@ -68,6 +68,7 @@ NRF52Bluetooth *nrf52Bluetooth;
|
||||||
|
|
||||||
#ifdef ARCH_PORTDUINO
|
#ifdef ARCH_PORTDUINO
|
||||||
#include "linux/LinuxHardwareI2C.h"
|
#include "linux/LinuxHardwareI2C.h"
|
||||||
|
#include "mesh/raspihttp/PiWebServer.h"
|
||||||
#include "platform/portduino/PortduinoGlue.h"
|
#include "platform/portduino/PortduinoGlue.h"
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
@ -857,6 +858,11 @@ void setup()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ARCH_PORTDUINO
|
#ifdef ARCH_PORTDUINO
|
||||||
|
#if __has_include(<ulfius.h>)
|
||||||
|
if (settingsMap[webserverport] != -1) {
|
||||||
|
piwebServerThread = new PiWebServerThread();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
initApiServer(TCPPort);
|
initApiServer(TCPPort);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,530 @@
|
||||||
|
/*
|
||||||
|
Adds a WebServer and WebService callbacks to meshtastic as Linux Version. The WebServer & Webservices
|
||||||
|
runs in a real linux thread beside the portdunio threading emulation. It replaces the complete ESP32
|
||||||
|
Webserver libs including generation of SSL certifcicates, because the use ESP specific details in
|
||||||
|
the lib that can't be emulated.
|
||||||
|
|
||||||
|
The WebServices adapt to the two major phoneapi functions "handleAPIv1FromRadio,handleAPIv1ToRadio"
|
||||||
|
The WebServer just adds basaic support to deliver WebContent, so it can be used to
|
||||||
|
deliver the WebGui definded by the WebClient Project.
|
||||||
|
|
||||||
|
Steps to get it running:
|
||||||
|
1.) Add these Linux Libs to the compile and target machine:
|
||||||
|
|
||||||
|
sudo apt update && \
|
||||||
|
apt -y install openssl libssl-dev libopenssl libsdl2-dev \
|
||||||
|
libulfius-dev liborcania-dev
|
||||||
|
|
||||||
|
2.) Configure the root directory of the web Content in the config.yaml file.
|
||||||
|
The followinng tags should be included and set at your needs
|
||||||
|
|
||||||
|
Example entry in the config.yaml
|
||||||
|
Webserver:
|
||||||
|
Port: 9001 # Port for Webserver & Webservices
|
||||||
|
RootPath: /home/marc/web # Root Dir of WebServer
|
||||||
|
|
||||||
|
3.) Checkout the web project
|
||||||
|
https://github.com/meshtastic/web.git
|
||||||
|
|
||||||
|
Build it and copy the content of the folder web/dist/* to the folder you did set as "RootPath"
|
||||||
|
|
||||||
|
!!!The WebServer should not be used as production system or exposed to the Internet. Its a raw basic version!!!
|
||||||
|
|
||||||
|
Author: Marc Philipp Hammermann
|
||||||
|
mail: marchammermann@googlemail.com
|
||||||
|
|
||||||
|
*/
|
||||||
|
#ifdef PORTDUINO_LINUX_HARDWARE
|
||||||
|
#if __has_include(<ulfius.h>)
|
||||||
|
#include "PiWebServer.h"
|
||||||
|
#include "NodeDB.h"
|
||||||
|
#include "PhoneAPI.h"
|
||||||
|
#include "PowerFSM.h"
|
||||||
|
#include "RadioLibInterface.h"
|
||||||
|
#include "airtime.h"
|
||||||
|
#include "graphics/Screen.h"
|
||||||
|
#include "main.h"
|
||||||
|
#include "mesh/wifi/WiFiAPClient.h"
|
||||||
|
#include "sleep.h"
|
||||||
|
#include <openssl/bn.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/pem.h>
|
||||||
|
#include <openssl/rsa.h>
|
||||||
|
#include <openssl/x509.h>
|
||||||
|
#include <orcania.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ulfius.h>
|
||||||
|
#include <yder.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "PortduinoFS.h"
|
||||||
|
#include "platform/portduino/PortduinoGlue.h"
|
||||||
|
|
||||||
|
#define DEFAULT_REALM "default_realm"
|
||||||
|
#define PREFIX ""
|
||||||
|
|
||||||
|
struct _file_config configWeb;
|
||||||
|
|
||||||
|
// We need to specify some content-type mapping, so the resources get delivered with the
|
||||||
|
// right content type and are displayed correctly in the browser
|
||||||
|
char contentTypes[][2][32] = {{".txt", "text/plain"}, {".html", "text/html"},
|
||||||
|
{".js", "text/javascript"}, {".png", "image/png"},
|
||||||
|
{".jpg", "image/jpg"}, {".gz", "application/gzip"},
|
||||||
|
{".gif", "image/gif"}, {".json", "application/json"},
|
||||||
|
{".css", "text/css"}, {".ico", "image/vnd.microsoft.icon"},
|
||||||
|
{".svg", "image/svg+xml"}, {".ts", "text/javascript"},
|
||||||
|
{".tsx", "text/javascript"}, {"", ""}};
|
||||||
|
|
||||||
|
#undef str
|
||||||
|
|
||||||
|
volatile bool isWebServerReady;
|
||||||
|
volatile bool isCertReady;
|
||||||
|
|
||||||
|
HttpAPI webAPI;
|
||||||
|
|
||||||
|
PiWebServerThread *piwebServerThread;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the filename extension
|
||||||
|
*/
|
||||||
|
const char *get_filename_ext(const char *path)
|
||||||
|
{
|
||||||
|
const char *dot = strrchr(path, '.');
|
||||||
|
if (!dot || dot == path)
|
||||||
|
return "*";
|
||||||
|
if (strchr(dot, '?') != NULL) {
|
||||||
|
//*strchr(dot, '?') = '\0';
|
||||||
|
const char *empty = "\0";
|
||||||
|
return empty;
|
||||||
|
}
|
||||||
|
return dot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Streaming callback function to ease sending large files
|
||||||
|
*/
|
||||||
|
static ssize_t callback_static_file_stream(void *cls, uint64_t pos, char *buf, size_t max)
|
||||||
|
{
|
||||||
|
(void)(pos);
|
||||||
|
if (cls != NULL) {
|
||||||
|
return fread(buf, 1, max, (FILE *)cls);
|
||||||
|
} else {
|
||||||
|
return U_STREAM_END;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup FILE* structure when streaming is complete
|
||||||
|
*/
|
||||||
|
static void callback_static_file_stream_free(void *cls)
|
||||||
|
{
|
||||||
|
if (cls != NULL) {
|
||||||
|
fclose((FILE *)cls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* static file callback endpoint that delivers the content for WebServer calls
|
||||||
|
*/
|
||||||
|
int callback_static_file(const struct _u_request *request, struct _u_response *response, void *user_data)
|
||||||
|
{
|
||||||
|
size_t length;
|
||||||
|
FILE *f;
|
||||||
|
char *file_requested, *file_path, *url_dup_save, *real_path = NULL;
|
||||||
|
const char *content_type;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Comment this if statement if you don't access static files url from root dir, like /app
|
||||||
|
*/
|
||||||
|
if (request->callback_position > 0) {
|
||||||
|
return U_CALLBACK_CONTINUE;
|
||||||
|
} else if (user_data != NULL && (configWeb.files_path != NULL)) {
|
||||||
|
file_requested = o_strdup(request->http_url);
|
||||||
|
url_dup_save = file_requested;
|
||||||
|
|
||||||
|
while (file_requested[0] == '/') {
|
||||||
|
file_requested++;
|
||||||
|
}
|
||||||
|
file_requested += o_strlen(configWeb.url_prefix);
|
||||||
|
while (file_requested[0] == '/') {
|
||||||
|
file_requested++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strchr(file_requested, '#') != NULL) {
|
||||||
|
*strchr(file_requested, '#') = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strchr(file_requested, '?') != NULL) {
|
||||||
|
*strchr(file_requested, '?') = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_requested == NULL || o_strlen(file_requested) == 0 || 0 == o_strcmp("/", file_requested)) {
|
||||||
|
o_free(url_dup_save);
|
||||||
|
url_dup_save = file_requested = o_strdup("index.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
file_path = msprintf("%s/%s", configWeb.files_path, file_requested);
|
||||||
|
real_path = realpath(file_path, NULL);
|
||||||
|
if (0 == o_strncmp(configWeb.files_path, real_path, o_strlen(configWeb.files_path))) {
|
||||||
|
if (access(file_path, F_OK) != -1) {
|
||||||
|
f = fopen(file_path, "rb");
|
||||||
|
if (f) {
|
||||||
|
fseek(f, 0, SEEK_END);
|
||||||
|
length = ftell(f);
|
||||||
|
fseek(f, 0, SEEK_SET);
|
||||||
|
|
||||||
|
content_type = u_map_get_case(&configWeb.mime_types, get_filename_ext(file_requested));
|
||||||
|
if (content_type == NULL) {
|
||||||
|
content_type = u_map_get(&configWeb.mime_types, "*");
|
||||||
|
LOG_DEBUG("Static File Server - Unknown mime type for extension %s \n", get_filename_ext(file_requested));
|
||||||
|
}
|
||||||
|
u_map_put(response->map_header, "Content-Type", content_type);
|
||||||
|
u_map_copy_into(response->map_header, &configWeb.map_header);
|
||||||
|
|
||||||
|
if (ulfius_set_stream_response(response, 200, callback_static_file_stream, callback_static_file_stream_free,
|
||||||
|
length, STATIC_FILE_CHUNK, f) != U_OK) {
|
||||||
|
LOG_DEBUG("callback_static_file - Error ulfius_set_stream_response\n ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (configWeb.redirect_on_404 == NULL) {
|
||||||
|
ulfius_set_string_body_response(response, 404, "File not found");
|
||||||
|
} else {
|
||||||
|
ulfius_add_header_to_response(response, "Location", configWeb.redirect_on_404);
|
||||||
|
response->status = 302;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (configWeb.redirect_on_404 == NULL) {
|
||||||
|
ulfius_set_string_body_response(response, 404, "File not found");
|
||||||
|
} else {
|
||||||
|
ulfius_add_header_to_response(response, "Location", configWeb.redirect_on_404);
|
||||||
|
response->status = 302;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
o_free(file_path);
|
||||||
|
o_free(url_dup_save);
|
||||||
|
free(real_path); // realpath uses malloc
|
||||||
|
return U_CALLBACK_CONTINUE;
|
||||||
|
} else {
|
||||||
|
LOG_DEBUG("Static File Server - Error, user_data is NULL or inconsistent\n");
|
||||||
|
return U_CALLBACK_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handleWebResponse() {}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Adapt the radioapi to the Webservice handleAPIv1ToRadio
|
||||||
|
* Trigger : WebGui(SAVE)->WebServcice->phoneApi
|
||||||
|
*/
|
||||||
|
int handleAPIv1ToRadio(const struct _u_request *req, struct _u_response *res, void *user_data)
|
||||||
|
{
|
||||||
|
LOG_DEBUG("handleAPIv1ToRadio web -> radio \n");
|
||||||
|
|
||||||
|
ulfius_add_header_to_response(res, "Content-Type", "application/x-protobuf");
|
||||||
|
ulfius_add_header_to_response(res, "Access-Control-Allow-Headers", "Content-Type");
|
||||||
|
ulfius_add_header_to_response(res, "Access-Control-Allow-Origin", "*");
|
||||||
|
ulfius_add_header_to_response(res, "Access-Control-Allow-Methods", "PUT, OPTIONS");
|
||||||
|
ulfius_add_header_to_response(res, "X-Protobuf-Schema",
|
||||||
|
"https://raw.githubusercontent.com/meshtastic/protobufs/master/mesh.proto");
|
||||||
|
|
||||||
|
if (req->http_verb == "OPTIONS") {
|
||||||
|
ulfius_set_response_properties(res, U_OPT_STATUS, 204);
|
||||||
|
return U_CALLBACK_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte buffer[MAX_TO_FROM_RADIO_SIZE];
|
||||||
|
size_t s = req->binary_body_length;
|
||||||
|
|
||||||
|
memcpy(buffer, req->binary_body, MAX_TO_FROM_RADIO_SIZE);
|
||||||
|
|
||||||
|
// FIXME* Problem with portdunio loosing mountpoint maybe because of running in a real sep. thread
|
||||||
|
|
||||||
|
portduinoVFS->mountpoint("/home/marc/.portduino/default");
|
||||||
|
|
||||||
|
LOG_DEBUG("Received %d bytes from PUT request\n", s);
|
||||||
|
webAPI.handleToRadio(buffer, s);
|
||||||
|
LOG_DEBUG("end web->radio \n");
|
||||||
|
return U_CALLBACK_COMPLETE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Adapt the radioapi to the Webservice handleAPIv1FromRadio
|
||||||
|
* Trigger : WebGui(POLL)->handleAPIv1FromRadio->phoneapi->Meshtastic(Radio) events
|
||||||
|
*/
|
||||||
|
int handleAPIv1FromRadio(const struct _u_request *req, struct _u_response *res, void *user_data)
|
||||||
|
{
|
||||||
|
|
||||||
|
// LOG_DEBUG("handleAPIv1FromRadio radio -> web\n");
|
||||||
|
std::string valueAll;
|
||||||
|
|
||||||
|
// Status code is 200 OK by default.
|
||||||
|
ulfius_add_header_to_response(res, "Content-Type", "application/x-protobuf");
|
||||||
|
ulfius_add_header_to_response(res, "Access-Control-Allow-Origin", "*");
|
||||||
|
ulfius_add_header_to_response(res, "Access-Control-Allow-Methods", "GET");
|
||||||
|
ulfius_add_header_to_response(res, "X-Protobuf-Schema",
|
||||||
|
"https://raw.githubusercontent.com/meshtastic/protobufs/master/mesh.proto");
|
||||||
|
|
||||||
|
uint8_t txBuf[MAX_STREAM_BUF_SIZE];
|
||||||
|
uint32_t len = 1;
|
||||||
|
|
||||||
|
if (valueAll == "true") {
|
||||||
|
while (len) {
|
||||||
|
len = webAPI.getFromRadio(txBuf);
|
||||||
|
ulfius_set_response_properties(res, U_OPT_STATUS, 200, U_OPT_BINARY_BODY, txBuf, len);
|
||||||
|
const char *tmpa = (const char *)txBuf;
|
||||||
|
ulfius_set_string_body_response(res, 200, tmpa);
|
||||||
|
// LOG_DEBUG("\n----webAPI response all:----\n");
|
||||||
|
LOG_DEBUG(tmpa);
|
||||||
|
LOG_DEBUG("\n");
|
||||||
|
}
|
||||||
|
// Otherwise, just return one protobuf
|
||||||
|
} else {
|
||||||
|
len = webAPI.getFromRadio(txBuf);
|
||||||
|
const char *tmpa = (const char *)txBuf;
|
||||||
|
ulfius_set_binary_body_response(res, 200, tmpa, len);
|
||||||
|
// LOG_DEBUG("\n----webAPI response:\n");
|
||||||
|
LOG_DEBUG(tmpa);
|
||||||
|
LOG_DEBUG("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOG_DEBUG("end radio->web\n", len);
|
||||||
|
return U_CALLBACK_COMPLETE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
OpenSSL RSA Key Gen
|
||||||
|
*/
|
||||||
|
int generate_rsa_key(EVP_PKEY **pkey)
|
||||||
|
{
|
||||||
|
EVP_PKEY_CTX *pkey_ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
|
||||||
|
if (!pkey_ctx)
|
||||||
|
return -1;
|
||||||
|
if (EVP_PKEY_keygen_init(pkey_ctx) <= 0)
|
||||||
|
return -1;
|
||||||
|
if (EVP_PKEY_CTX_set_rsa_keygen_bits(pkey_ctx, 2048) <= 0)
|
||||||
|
return -1;
|
||||||
|
if (EVP_PKEY_keygen(pkey_ctx, pkey) <= 0)
|
||||||
|
return -1;
|
||||||
|
EVP_PKEY_CTX_free(pkey_ctx);
|
||||||
|
return 0; // SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
int generate_self_signed_x509(EVP_PKEY *pkey, X509 **x509)
|
||||||
|
{
|
||||||
|
*x509 = X509_new();
|
||||||
|
if (!*x509)
|
||||||
|
return -1;
|
||||||
|
if (X509_set_version(*x509, 2) != 1)
|
||||||
|
return -1;
|
||||||
|
ASN1_INTEGER_set(X509_get_serialNumber(*x509), 1);
|
||||||
|
X509_gmtime_adj(X509_get_notBefore(*x509), 0);
|
||||||
|
X509_gmtime_adj(X509_get_notAfter(*x509), 31536000L); // 1 YEAR ACCESS
|
||||||
|
|
||||||
|
X509_set_pubkey(*x509, pkey);
|
||||||
|
|
||||||
|
// SET Subject Name
|
||||||
|
X509_NAME *name = X509_get_subject_name(*x509);
|
||||||
|
X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *)"DE", -1, -1, 0);
|
||||||
|
X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *)"Meshtastic", -1, -1, 0);
|
||||||
|
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)"meshtastic.local", -1, -1, 0);
|
||||||
|
// Selfsigned, Issuer = Subject
|
||||||
|
X509_set_issuer_name(*x509, name);
|
||||||
|
|
||||||
|
// Certificate signed with our privte key
|
||||||
|
if (X509_sign(*x509, pkey, EVP_sha256()) <= 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *read_file_into_string(const char *filename)
|
||||||
|
{
|
||||||
|
FILE *file = fopen(filename, "rb");
|
||||||
|
if (file == NULL) {
|
||||||
|
LOG_ERROR("Error reading File : %s \n", filename);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size of file
|
||||||
|
fseek(file, 0, SEEK_END);
|
||||||
|
long filesize = ftell(file);
|
||||||
|
rewind(file);
|
||||||
|
|
||||||
|
// reserve mem for file + 1 byte
|
||||||
|
char *buffer = (char *)malloc(filesize + 1);
|
||||||
|
if (buffer == NULL) {
|
||||||
|
LOG_ERROR("Malloc of mem failed for file : %s \n", filename);
|
||||||
|
fclose(file);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read content
|
||||||
|
size_t readSize = fread(buffer, 1, filesize, file);
|
||||||
|
if (readSize != filesize) {
|
||||||
|
LOG_ERROR("Error reading file into buffer\n");
|
||||||
|
free(buffer);
|
||||||
|
fclose(file);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add terminator sign at the end
|
||||||
|
buffer[filesize] = '\0';
|
||||||
|
fclose(file);
|
||||||
|
return buffer; // return pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
int PiWebServerThread::CheckSSLandLoad()
|
||||||
|
{
|
||||||
|
// read certificate
|
||||||
|
cert_pem = read_file_into_string("certificate.pem");
|
||||||
|
if (cert_pem == NULL) {
|
||||||
|
LOG_ERROR("ERROR SSL Certificate File can't be loaded or is missing\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// read private key
|
||||||
|
key_pem = read_file_into_string("private_key.pem");
|
||||||
|
if (key_pem == NULL) {
|
||||||
|
LOG_ERROR("ERROR file private_key can't be loaded or is missing\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int PiWebServerThread::CreateSSLCertificate()
|
||||||
|
{
|
||||||
|
|
||||||
|
EVP_PKEY *pkey = NULL;
|
||||||
|
X509 *x509 = NULL;
|
||||||
|
|
||||||
|
if (generate_rsa_key(&pkey) != 0) {
|
||||||
|
LOG_ERROR("Error generating RSA-Key.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (generate_self_signed_x509(pkey, &x509) != 0) {
|
||||||
|
LOG_ERROR("Error generating of X509-Certificat.\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ope file to write private key file
|
||||||
|
FILE *pkey_file = fopen("private_key.pem", "wb");
|
||||||
|
if (!pkey_file) {
|
||||||
|
LOG_ERROR("Error opening private key file.\n");
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
// write private key file
|
||||||
|
PEM_write_PrivateKey(pkey_file, pkey, NULL, NULL, 0, NULL, NULL);
|
||||||
|
fclose(pkey_file);
|
||||||
|
|
||||||
|
// open Certificate file
|
||||||
|
FILE *x509_file = fopen("certificate.pem", "wb");
|
||||||
|
if (!x509_file) {
|
||||||
|
LOG_ERROR("Error opening certificate.\n");
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
// write cirtificate
|
||||||
|
PEM_write_X509(x509_file, x509);
|
||||||
|
fclose(x509_file);
|
||||||
|
|
||||||
|
EVP_PKEY_free(pkey);
|
||||||
|
X509_free(x509);
|
||||||
|
LOG_INFO("Create SSL Certifictate -certificate.pem- succesfull \n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void initWebServer() {}
|
||||||
|
|
||||||
|
PiWebServerThread::PiWebServerThread()
|
||||||
|
{
|
||||||
|
int ret, retssl, webservport;
|
||||||
|
|
||||||
|
if (CheckSSLandLoad() != 0) {
|
||||||
|
CreateSSLCertificate();
|
||||||
|
if (CheckSSLandLoad() != 0) {
|
||||||
|
LOG_ERROR("Major Error Gen & Read SSL Certificate\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settingsMap[webserverport] != 0) {
|
||||||
|
webservport = settingsMap[webserverport];
|
||||||
|
LOG_INFO("Using webserver port from yaml config. %i \n", webservport);
|
||||||
|
} else {
|
||||||
|
LOG_INFO("Webserver port in yaml config set to 0, so defaulting to port 443.\n");
|
||||||
|
webservport = 443;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Web Content Service Instance
|
||||||
|
if (ulfius_init_instance(&instanceWeb, webservport, NULL, DEFAULT_REALM) != U_OK) {
|
||||||
|
LOG_ERROR("Webserver couldn't be started, abort execution\n");
|
||||||
|
} else {
|
||||||
|
|
||||||
|
LOG_INFO("Webserver started ....\n");
|
||||||
|
u_map_init(&configWeb.mime_types);
|
||||||
|
u_map_put(&configWeb.mime_types, "*", "application/octet-stream");
|
||||||
|
u_map_put(&configWeb.mime_types, ".html", "text/html");
|
||||||
|
u_map_put(&configWeb.mime_types, ".htm", "text/html");
|
||||||
|
u_map_put(&configWeb.mime_types, ".tsx", "application/javascript");
|
||||||
|
u_map_put(&configWeb.mime_types, ".ts", "application/javascript");
|
||||||
|
u_map_put(&configWeb.mime_types, ".css", "text/css");
|
||||||
|
u_map_put(&configWeb.mime_types, ".js", "application/javascript");
|
||||||
|
u_map_put(&configWeb.mime_types, ".json", "application/json");
|
||||||
|
u_map_put(&configWeb.mime_types, ".png", "image/png");
|
||||||
|
u_map_put(&configWeb.mime_types, ".gif", "image/gif");
|
||||||
|
u_map_put(&configWeb.mime_types, ".jpeg", "image/jpeg");
|
||||||
|
u_map_put(&configWeb.mime_types, ".jpg", "image/jpeg");
|
||||||
|
u_map_put(&configWeb.mime_types, ".ttf", "font/ttf");
|
||||||
|
u_map_put(&configWeb.mime_types, ".woff", "font/woff");
|
||||||
|
u_map_put(&configWeb.mime_types, ".ico", "image/x-icon");
|
||||||
|
u_map_put(&configWeb.mime_types, ".svg", "image/svg+xml");
|
||||||
|
|
||||||
|
webrootpath = settingsStrings[webserverrootpath];
|
||||||
|
|
||||||
|
configWeb.files_path = (char *)webrootpath.c_str();
|
||||||
|
configWeb.url_prefix = "";
|
||||||
|
configWeb.rootPath = strdup(portduinoVFS->mountpoint());
|
||||||
|
|
||||||
|
u_map_put(instanceWeb.default_headers, "Access-Control-Allow-Origin", "*");
|
||||||
|
// Maximum body size sent by the client is 1 Kb
|
||||||
|
instanceWeb.max_post_body_size = 1024;
|
||||||
|
ulfius_add_endpoint_by_val(&instanceWeb, "GET", PREFIX, "/api/v1/fromradio/*", 1, &handleAPIv1FromRadio, NULL);
|
||||||
|
ulfius_add_endpoint_by_val(&instanceWeb, "PUT", PREFIX, "/api/v1/toradio/*", 1, &handleAPIv1ToRadio, configWeb.rootPath);
|
||||||
|
|
||||||
|
// Add callback function to all endpoints for the Web Server
|
||||||
|
ulfius_add_endpoint_by_val(&instanceWeb, "GET", NULL, "/*", 2, &callback_static_file, &configWeb);
|
||||||
|
|
||||||
|
// thats for serving without SSL
|
||||||
|
// retssl = ulfius_start_framework(&instanceWeb);
|
||||||
|
|
||||||
|
// thats for serving with SSL
|
||||||
|
retssl = ulfius_start_secure_framework(&instanceWeb, key_pem, cert_pem);
|
||||||
|
|
||||||
|
if (retssl == U_OK) {
|
||||||
|
LOG_INFO("Web Server framework started on port: %i \n", webservport);
|
||||||
|
LOG_INFO("Web Server root %s\n", (char *)webrootpath.c_str());
|
||||||
|
} else {
|
||||||
|
LOG_ERROR("Error starting Web Server framework\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PiWebServerThread::~PiWebServerThread()
|
||||||
|
{
|
||||||
|
u_map_clean(&configWeb.mime_types);
|
||||||
|
|
||||||
|
ulfius_stop_framework(&instanceWeb);
|
||||||
|
ulfius_stop_framework(&instanceWeb);
|
||||||
|
free(configWeb.rootPath);
|
||||||
|
ulfius_clean_instance(&instanceService);
|
||||||
|
ulfius_clean_instance(&instanceService);
|
||||||
|
free(cert_pem);
|
||||||
|
LOG_INFO("End framework");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#endif
|
|
@ -0,0 +1,61 @@
|
||||||
|
#pragma once
|
||||||
|
#ifdef PORTDUINO_LINUX_HARDWARE
|
||||||
|
#if __has_include(<ulfius.h>)
|
||||||
|
#include "PhoneAPI.h"
|
||||||
|
#include "ulfius-cfg.h"
|
||||||
|
#include "ulfius.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#define STATIC_FILE_CHUNK 256
|
||||||
|
|
||||||
|
void initWebServer();
|
||||||
|
void createSSLCert();
|
||||||
|
int callback_static_file(const struct _u_request *request, struct _u_response *response, void *user_data);
|
||||||
|
const char *get_filename_ext(const char *path);
|
||||||
|
|
||||||
|
struct _file_config {
|
||||||
|
char *files_path;
|
||||||
|
char *url_prefix;
|
||||||
|
struct _u_map mime_types;
|
||||||
|
struct _u_map map_header;
|
||||||
|
char *redirect_on_404;
|
||||||
|
char *rootPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PiWebServerThread
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
char *key_pem = NULL;
|
||||||
|
char *cert_pem = NULL;
|
||||||
|
// struct _u_map mime_types;
|
||||||
|
std::string webrootpath;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PiWebServerThread();
|
||||||
|
~PiWebServerThread();
|
||||||
|
int CreateSSLCertificate();
|
||||||
|
int CheckSSLandLoad();
|
||||||
|
uint32_t requestRestart = 0;
|
||||||
|
struct _u_instance instanceWeb;
|
||||||
|
struct _u_instance instanceService;
|
||||||
|
};
|
||||||
|
|
||||||
|
class HttpAPI : public PhoneAPI
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Nothing here yet
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Nothing here yet
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Check the current underlying physical link to see if the client is currently connected
|
||||||
|
virtual bool checkIsConnected() override { return true; } // FIXME, be smarter about this
|
||||||
|
};
|
||||||
|
|
||||||
|
extern PiWebServerThread *piwebServerThread;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#endif
|
|
@ -195,6 +195,11 @@ void portduinoSetup()
|
||||||
settingsStrings[keyboardDevice] = (yamlConfig["Input"]["KeyboardDevice"]).as<std::string>("");
|
settingsStrings[keyboardDevice] = (yamlConfig["Input"]["KeyboardDevice"]).as<std::string>("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (yamlConfig["Webserver"]) {
|
||||||
|
settingsMap[webserverport] = (yamlConfig["Webserver"]["Port"]).as<int>(-1);
|
||||||
|
settingsStrings[webserverrootpath] = (yamlConfig["Webserver"]["RootPath"]).as<std::string>("");
|
||||||
|
}
|
||||||
|
|
||||||
} catch (YAML::Exception e) {
|
} catch (YAML::Exception e) {
|
||||||
std::cout << "*** Exception " << e.what() << std::endl;
|
std::cout << "*** Exception " << e.what() << std::endl;
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
|
|
|
@ -33,7 +33,10 @@ enum configNames {
|
||||||
displayOffsetY,
|
displayOffsetY,
|
||||||
displayInvert,
|
displayInvert,
|
||||||
keyboardDevice,
|
keyboardDevice,
|
||||||
logoutputlevel
|
logoutputlevel,
|
||||||
|
webserver,
|
||||||
|
webserverport,
|
||||||
|
webserverrootpath
|
||||||
};
|
};
|
||||||
enum { no_screen, st7789, st7735, st7735s, ili9341 };
|
enum { no_screen, st7789, st7735, st7735s, ili9341 };
|
||||||
enum { no_touchscreen, xpt2046, stmpe610 };
|
enum { no_touchscreen, xpt2046, stmpe610 };
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
[env:native]
|
[env:native]
|
||||||
extends = portduino_base
|
extends = portduino_base
|
||||||
build_flags = ${portduino_base.build_flags} -O0 -I variants/portduino
|
; The pkg-config commands below optionally add link flags.
|
||||||
|
; the || : is just a "or run the null command" to avoid returning an error code
|
||||||
|
build_flags = ${portduino_base.build_flags} -O0 -I variants/portduino -I /usr/include
|
||||||
|
!pkg-config --libs libulfius --silence-errors || :
|
||||||
|
!pkg-config --libs openssl --silence-errors || :
|
||||||
board = cross_platform
|
board = cross_platform
|
||||||
lib_deps = ${portduino_base.lib_deps}
|
lib_deps = ${portduino_base.lib_deps}
|
||||||
build_src_filter = ${portduino_base.build_src_filter}
|
build_src_filter = ${portduino_base.build_src_filter}
|
||||||
|
|
Ładowanie…
Reference in New Issue