mirror of
https://github.com/meshtastic/firmware.git
synced 2025-04-22 16:56:53 +00:00
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>
This commit is contained in:
parent
9d37a8d17f
commit
e174328de3
2
.gitignore
vendored
2
.gitignore
vendored
@ -31,3 +31,5 @@ venv/
|
||||
release/
|
||||
.vscode/extensions.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.
|
||||
|
||||
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
|
||||
debug_init_break = tbreak setup
|
||||
|
@ -2,7 +2,7 @@
|
||||
extends = esp32_base
|
||||
|
||||
build_src_filter =
|
||||
${esp32_base.build_src_filter} -<nimble/>
|
||||
${esp32_base.build_src_filter} -<nimble/> -<mesh/raspihttp>
|
||||
|
||||
monitor_speed = 115200
|
||||
|
||||
@ -12,5 +12,4 @@ build_flags =
|
||||
|
||||
lib_ignore =
|
||||
${esp32_base.lib_ignore}
|
||||
NimBLE-Arduino
|
||||
|
||||
NimBLE-Arduino
|
@ -11,7 +11,7 @@ build_flags =
|
||||
-Isrc/platform/nrf52
|
||||
|
||||
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=
|
||||
${arduino_base.lib_deps}
|
||||
|
@ -12,6 +12,7 @@ build_src_filter =
|
||||
-<platform/rp2040>
|
||||
-<mesh/wifi/>
|
||||
-<mesh/http/>
|
||||
+<mesh/raspihttp/>
|
||||
-<mesh/eth/>
|
||||
-<modules/esp32>
|
||||
-<modules/Telemetry/EnvironmentTelemetry.cpp>
|
||||
|
@ -12,7 +12,7 @@ build_flags =
|
||||
-D__PLAT_RP2040__
|
||||
# -D _POSIX_THREADS
|
||||
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 =
|
||||
BluetoothOTA
|
||||
|
@ -13,7 +13,7 @@ build_flags =
|
||||
-DVECT_TAB_OFFSET=0x08000000
|
||||
|
||||
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
|
||||
upload_protocol = stlink
|
||||
|
@ -117,3 +117,7 @@ Input:
|
||||
|
||||
Logging:
|
||||
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
|
||||
#include "linux/LinuxHardwareI2C.h"
|
||||
#include "mesh/raspihttp/PiWebServer.h"
|
||||
#include "platform/portduino/PortduinoGlue.h"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
@ -857,6 +858,11 @@ void setup()
|
||||
#endif
|
||||
|
||||
#ifdef ARCH_PORTDUINO
|
||||
#if __has_include(<ulfius.h>)
|
||||
if (settingsMap[webserverport] != -1) {
|
||||
piwebServerThread = new PiWebServerThread();
|
||||
}
|
||||
#endif
|
||||
initApiServer(TCPPort);
|
||||
#endif
|
||||
|
||||
|
530
src/mesh/raspihttp/PiWebServer.cpp
Normal file
530
src/mesh/raspihttp/PiWebServer.cpp
Normal file
@ -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
|
61
src/mesh/raspihttp/PiWebServer.h
Normal file
61
src/mesh/raspihttp/PiWebServer.h
Normal file
@ -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>("");
|
||||
}
|
||||
|
||||
if (yamlConfig["Webserver"]) {
|
||||
settingsMap[webserverport] = (yamlConfig["Webserver"]["Port"]).as<int>(-1);
|
||||
settingsStrings[webserverrootpath] = (yamlConfig["Webserver"]["RootPath"]).as<std::string>("");
|
||||
}
|
||||
|
||||
} catch (YAML::Exception e) {
|
||||
std::cout << "*** Exception " << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
|
@ -33,7 +33,10 @@ enum configNames {
|
||||
displayOffsetY,
|
||||
displayInvert,
|
||||
keyboardDevice,
|
||||
logoutputlevel
|
||||
logoutputlevel,
|
||||
webserver,
|
||||
webserverport,
|
||||
webserverrootpath
|
||||
};
|
||||
enum { no_screen, st7789, st7735, st7735s, ili9341 };
|
||||
enum { no_touchscreen, xpt2046, stmpe610 };
|
||||
|
@ -1,6 +1,10 @@
|
||||
[env:native]
|
||||
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
|
||||
lib_deps = ${portduino_base.lib_deps}
|
||||
build_src_filter = ${portduino_base.build_src_filter}
|
||||
build_src_filter = ${portduino_base.build_src_filter}
|
||||
|
Loading…
Reference in New Issue
Block a user