Skip to content

Commit be96850

Browse files
fhirschmannclaude
andcommitted
feat: FTP/WebDAV accept any username (password-only auth)
With one shared device password the username is pointless, so both file servers now authenticate by password only - any username is accepted, a wrong password is still rejected, and an empty device password leaves them open. - FTP: vendor ESP-FTP-Server-Lib into lib/ (dropped from lib_deps) and patch c_USER/c_PASS to accept any username and match only the password (empty password = open). - WebDAV: the self-contained server now base64-decodes the HTTP-Basic header and compares only the password (any username), open when no password is set. - Drop the now-pointless username fields from the FTP and WebDAV tabs and add a hint; the WebDAV settings POST only carries the auto-start flag. Locales (de/en/fr) and README updated. Verified on device: FTP login with arbitrary usernames + the device password works, wrong password is rejected, empty password opens it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent dc4cf21 commit be96850

40 files changed

Lines changed: 1920 additions & 63 deletions

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ neon logo that doubles as the SVG favicon ([`7be5254`](../../commit/7be5254)):
109109
| **Configurable branding**: re-brand the navbar header + footer (General → Branding) with a live preview as you type — empty keeps the "Leo Industries" default, so the fork is easy to re-brand | [`fd57fcb`](../../commit/fd57fcb) |
110110
| **Password protection**: single password (no username), 90-day session cookie, brute-force lockout, logout entry; off in hotspot mode. Scripts/API clients authenticate with the password as an **API key** (`X-API-Key` header / `apikey` query param) | [`e74e712`](../../commit/e74e712) |
111111
| **One shared device password (Security tab)**: the password fields were pulled out of the WiFi/FTP/WebDAV tabs into a single **Security** sub-tab — one password now protects the web interface, FTP *and* WebDAV (set once, applies everywhere; applied live without a reboot). An **empty password is accepted** and disables protection for all three. The **hostname** moved to the top of the **General** tab (saved on its own; `/wificonfig` now does partial updates) and the navbar brand reads just **Leo Industries** | [`c6dfc67`](../../commit/c6dfc67) |
112+
| **FTP/WebDAV accept any username**: with one shared password the username is pointless, so both servers now authenticate by **password only — any username is accepted** (wrong password still rejected; empty device password = open). The username fields are gone from both tabs. FTP uses a **vendored, patched copy** of ESP-FTP-Server-Lib in [`lib/`](lib/ESP-FTP-Server-Lib) (password-only auth); WebDAV's self-contained server decodes the HTTP-Basic header and compares only the password | [`PENDINGHASH3`](../../commit/PENDINGHASH3) |
112113
| **One-click OTA + version badge**: a Tools-tab button (also bindable command **186** / MQTT `firmware_update`) pulls the rolling `latest` GitHub release and flashes it over OTA; a navbar badge shows the running build and turns amber when an update is available (passive `/version` check) — click it to install | [`8527f5e`](../../commit/8527f5e) · [`b736abc`](../../commit/b736abc) |
113114
| **HTTP file sync**: pull audio files from a web server onto the SD card from a JSON manifest — additive, streamed in chunks straight to SD, background task with live progress + stop, abort-on-button, stall watchdog, auto-pauses playback and keeps the device awake mid-transfer. The manifest is streamed into the parser to halve peak RAM | [`ac24bbc`](../../commit/ac24bbc) · [`42d2c46`](../../commit/42d2c46) |
114115
| **WebDAV server**: mount the SD card as a network drive (`http://<ip>:81/`) to copy audio on/off it straight from Finder/Explorer — no FTP client needed. Self-contained server (OPTIONS/PROPFIND/GET/HEAD+ranges/PUT/DELETE/MKCOL/MOVE/COPY/LOCK) running in its own task pinned to core 0 so transfers never disturb the audio pipeline; optional HTTP-Basic credentials. Configure + auto-start on boot in its own **WebDAV** settings sub-tab; start/stop live from the Control tab, command **188** or MQTT `webdav` (Home Assistant switch included). With auto-start on, the share is announced over **Bonjour/mDNS** (`_webdav._tcp`, `path=/`) so it pops up by itself in the Finder/Explorer network sidebar — no manual "Connect to Server" needed (currently disabled — see `WEBDAV_ENABLE`) | [`9b2bee0`](../../commit/9b2bee0) |

html/locales/de.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,7 @@
443443
},
444444
"ftp": {
445445
"title": "FTP-Einstellungen",
446+
"authHint": "FTP akzeptiert jeden Benutzernamen – nur das Passwort zählt. Das Passwort ist das gemeinsame Geräte-Passwort (Tab \"Sicherheit\"). Leeres Passwort = Zugriff ohne Anmeldung.",
446447
"user": {
447448
"title": "FTP-Benutzername",
448449
"placeholder": "Benutzername"
@@ -474,7 +475,7 @@
474475
"show": "Passwort anzeigen/verbergen"
475476
},
476477
"auth": {
477-
"hint": "Das WebDAV-Passwort wird im Tab \"Sicherheit\" gesetzt (gemeinsames Geräte-Passwort). Benutzername und Passwort leer lassen, um ohne Anmeldung zuzugreifen."
478+
"hint": "WebDAV akzeptiert jeden Benutzernamen – nur das Passwort zählt. Das Passwort ist das gemeinsame Geräte-Passwort (Tab \"Sicherheit\"). Leeres Passwort = Zugriff ohne Anmeldung."
478479
},
479480
"start": {
480481
"title": "WebDAV-Server starten",

html/locales/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,7 @@
443443
},
444444
"ftp": {
445445
"title": "FTP-settings",
446+
"authHint": "FTP accepts any username - only the password matters. The password is the shared device password (\"Security\" tab). An empty password means access without a login.",
446447
"user": {
447448
"title": "FTP-Username",
448449
"placeholder": "username"
@@ -474,7 +475,7 @@
474475
"show": "Show/hide password"
475476
},
476477
"auth": {
477-
"hint": "The WebDAV password is set on the \"Security\" tab (shared device password). Leave username and password empty to allow access without a login."
478+
"hint": "WebDAV accepts any username - only the password matters. The password is the shared device password (\"Security\" tab). An empty password means access without a login."
478479
},
479480
"start": {
480481
"title": "Start WebDAV server",

html/locales/fr.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,7 @@
436436
},
437437
"ftp": {
438438
"title": "Paramètres FTP",
439+
"authHint": "Le FTP accepte n'importe quel nom d'utilisateur - seul le mot de passe compte. C'est le mot de passe partagé de l'appareil (onglet « Sécurité »). Un mot de passe vide permet l'accès sans connexion.",
439440
"user": {
440441
"title": "Nom d'utilisateur FTP",
441442
"placeholder": "nom d'utilisateur"
@@ -467,7 +468,7 @@
467468
"show": "Afficher/masquer le mot de passe"
468469
},
469470
"auth": {
470-
"hint": "Le mot de passe WebDAV se règle dans l'onglet « Sécurité » (mot de passe partagé de l'appareil). Laissez le nom d'utilisateur et le mot de passe vides pour un accès sans connexion."
471+
"hint": "Le WebDAV accepte n'importe quel nom d'utilisateur - seul le mot de passe compte. C'est le mot de passe partagé de l'appareil (onglet « Sécurité »). Un mot de passe vide permet l'accès sans connexion."
471472
},
472473
"start": {
473474
"title": "Démarrer serveur WebDAV",

html/management.html

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3239,19 +3239,10 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5>
32393239
</div>
32403240
<div class="tab-pane fade" id="nav-ftp" role="tabpanel" aria-labelledby="nav-ftp-tab">
32413241
<div class="container" id="ftpConfig">
3242-
<form action="#ftpConfig" method="POST" onsubmit="ftpSettings('ftpConfig'); return false">
3243-
<div class="mb-3 col-md-12">
3244-
<legend data-i18n="ftp.title"></legend>
3245-
<label for="ftpUser" data-i18n="[prepend]ftp.user.title">:</label>
3246-
<input type="text" class="form-control" id="ftpUser" maxlength="0"
3247-
data-i18n="[placeholder]ftp.user.placeholder" name="ftpUser" value="" required>
3248-
<small class="form-text text-muted" data-i18n="ftp.pwd.securityHint"></small>
3249-
</div>
3250-
<div class="text-center">
3251-
<button type="reset" class="btn btn-warning" data-i18n="reset"></button>&nbsp;<button
3252-
type="submit" class="btn btn-primary" data-i18n="submit"></button>
3253-
</div>
3254-
</form>
3242+
<div class="mb-3 col-md-12">
3243+
<legend data-i18n="ftp.title"></legend>
3244+
<div class="mb-2"><small class="text-muted" data-i18n="ftp.authHint"></small></div>
3245+
</div>
32553246
<hr>
32563247
</div>
32573248
<div class="container" id="ftpStart">
@@ -3277,9 +3268,6 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5>
32773268
<input class="form-check-input" type="checkbox" id="webdavEnable" name="webdavEnable">
32783269
<label class="form-check-label" for="webdavEnable" data-i18n="webdav.enable"></label>
32793270
</div>
3280-
<label for="webdavUser" data-i18n="[prepend]webdav.user.title">:</label>
3281-
<input type="text" class="form-control" id="webdavUser" maxlength="0"
3282-
data-i18n="[placeholder]webdav.user.placeholder" name="webdavUser" value="">
32833271
<small class="text-muted" data-i18n="webdav.auth.hint"></small>
32843272
</div>
32853273
<div class="text-center">
@@ -5358,16 +5346,12 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5>
53585346
let ftpSettings = settings.ftp;
53595347
if (ftpSettings) {
53605348
document.getElementById('nav-ftp-tab').setAttribute('data-visible', true);
5361-
document.getElementById("ftpUser").value = (ftpSettings.username === "-1") ? "" : ftpSettings.username;
5362-
document.getElementById("ftpUser").setAttribute('maxlength', ftpSettings.maxUserLength);
53635349
}
53645350
// webdav (the sub-tab stays hidden unless the firmware reports WebDAV support)
53655351
let webdavSettingsData = settings.webdav;
53665352
if (webdavSettingsData) {
53675353
document.getElementById('nav-webdav-tab').setAttribute('data-visible', true);
53685354
document.getElementById('btn-webdav').style.display = '';
5369-
document.getElementById("webdavUser").value = (webdavSettingsData.username === "-1") ? "" : webdavSettingsData.username;
5370-
document.getElementById("webdavUser").setAttribute('maxlength', webdavSettingsData.maxUserLength);
53715355
document.getElementById("webdavEnable").checked = !!webdavSettingsData.enable;
53725356
const port = webdavSettingsData.port || 81;
53735357
document.getElementById("webdavMountUrl").textContent = "http://" + host + ":" + port + "/";
@@ -5973,22 +5957,6 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5>
59735957
toaster.info(i18next.t("toast.saved"));
59745958
}
59755959

5976-
async function ftpSettings(clickedId) {
5977-
lastIdclicked = clickedId;
5978-
var myObj = {
5979-
"ftp": {
5980-
username: document.getElementById('ftpUser').value
5981-
}
5982-
};
5983-
var myJSON = JSON.stringify(myObj);
5984-
await fetch("http://" + host + "/settings", {
5985-
method: "POST",
5986-
headers: { "Content-Type": "application/json" },
5987-
body: myJSON
5988-
});
5989-
toaster.info(i18next.t("toast.saved"));
5990-
}
5991-
59925960
function ftpSetStatus(start) {
59935961
var myObj = {
59945962
"ftpStatus": {
@@ -5999,12 +5967,11 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5>
59995967
socket.send(myJSON);
60005968
}
60015969

6002-
/* save the WebDAV credentials + auto-start-on-boot flag */
5970+
/* save the WebDAV auto-start-on-boot flag (no credentials: any username, password via Security tab) */
60035971
async function webdavSettings(clickedId) {
60045972
lastIdclicked = clickedId;
60055973
var myObj = {
60065974
"webdav": {
6007-
username: document.getElementById('webdavUser').value,
60085975
enable: document.getElementById('webdavEnable').checked
60095976
}
60105977
};

lib/ESP-FTP-Server-Lib/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2020 Peter Buchegger
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

lib/ESP-FTP-Server-Lib/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# ESP-FTP-Server-Lib
2+
3+
This is a fork form https://github.com/peterus/ESP-FTP-Server-Lib, since there seems to be no maintanance anymore.
4+
This library will provide a simple and modern FTP server for your ESP32 or ESP8266 device.
5+
You can setup multiple users and mutliple filesystems (SD-Card, MMC-Card or/and SPIFFS).
6+
7+
## Examples
8+
9+
In the example folder you can find a very simple usage of the FTP server. You just need to setup the users, add the filesystems which you want to use, and call the handle function in the loop.
10+
With the Compileflag -DENABLE_FTP_SANITIZATION you can enable support for special-characters like ":" or "?" by URL-Encodeing of Files.
11+
12+
## Known Commands to the server
13+
14+
Currently all kind of simple commands are known to the server:
15+
* CDUP
16+
* CLNT
17+
* CWD
18+
* DELE
19+
* FEAT
20+
* LISST
21+
* MKD
22+
* MLSD
23+
* NLST
24+
* OPTS
25+
* PASV
26+
* PORT
27+
* PWD
28+
* RETR
29+
* RMD
30+
* RNFR
31+
* RNTO
32+
* STAT
33+
* STOR
34+
* TYPE
35+
* USER
36+
* PASS
37+
* SYST
38+
* QUIT
39+
* ABOR
40+
41+
## What is still missing / TODO
42+
43+
Some commands are still missing, if you need them create a ticket :)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
name=ESP-FTP-Server-Lib
2+
version=0.14.1
3+
author=Peter Buchegger <peter@pbuchegger.at>
4+
maintainer=Peter Buchegger <peter@pbuchegger.at>
5+
sentence=Simple and modern FTP server for ESP devices.
6+
paragraph=With this library you can run a simple and modern FTP server on your ESP32 or ESP8266. You can mount multiple filesystems like SD-Card, MMC-Card or SPIFFS at the same time.
7+
category=Communication
8+
url=https://github.com/peterus/ESP-FTP-Server-Lib
9+
architectures=esp8266,esp32,arduino-esp32
10+
includes=ESP-FTP-Server-Lib.h
11+
depends=
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#ifndef CDUP_H_
2+
#define CDUP_H_
3+
4+
#include <WiFiClient.h>
5+
6+
#include "../FTPCommand.h"
7+
#include "../FTPResponseCodes.h"
8+
9+
class CDUP : public FTPCommand {
10+
public:
11+
explicit CDUP(WiFiClient *const Client) : FTPCommand("CDUP", 0, Client) {
12+
}
13+
14+
void run(FTPPath &WorkDirectory, const std::vector<String> &Line) override {
15+
WorkDirectory.goPathUp();
16+
SendResponse(FtpCodes::COMMAND_OK, "Ok. Current directory is " + WorkDirectory.getClearPath());
17+
}
18+
};
19+
20+
#endif
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#ifndef CWD_H_
2+
#define CWD_H_
3+
4+
#include <WiFiClient.h>
5+
6+
#include "../FTPCommand.h"
7+
#include "../FTPResponseCodes.h"
8+
9+
class CWD : public FTPCommand {
10+
public:
11+
explicit CWD(WiFiClient *const Client, FTPFilesystem *const Filesystem) : FTPCommand("CWD", 1, Client, Filesystem) {
12+
}
13+
14+
void run(FTPPath &WorkDirectory, const std::vector<String> &Line) override {
15+
FTPPath path = WorkDirectory;
16+
if (Line[1] == "..") {
17+
path.goPathUp();
18+
} else {
19+
path.changePath(Line[1]);
20+
}
21+
File dir = _Filesystem->open(path.getPath());
22+
if (dir.isDirectory()) {
23+
WorkDirectory = path;
24+
SendResponse(FtpCodes::COMMAND_OK, "Ok. Current directory is " + WorkDirectory.getClearPath());
25+
} else {
26+
SendResponse(FtpCodes::FILE_ACTION_NOT_TAKEN, "Directory does not exist");
27+
}
28+
}
29+
};
30+
31+
#endif

0 commit comments

Comments
 (0)