saltcorn-samba 0.4.5 → 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +86 -0
- package/index.js +63 -49
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,92 @@ All notable changes to `saltcorn-samba` are documented here.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [0.4.8] – 2026-07-05
|
|
8
|
+
|
|
9
|
+
### Fixed – **Basispfad wurde beim Verbindungstest doppelt vorangestellt (die eigentliche Ursache)**
|
|
10
|
+
|
|
11
|
+
In allen Vorgängerversionen (0.4.4 – 0.4.7) hat der Verbindungstest den
|
|
12
|
+
konfigurierten Basispfad **zweimal** auf den Server geschickt und deshalb
|
|
13
|
+
systematisch die Meldung *„Der Basispfad ist auf dem Server nicht auffindbar"*
|
|
14
|
+
produziert — auch dann, wenn der Pfad in Wirklichkeit existierte und mit
|
|
15
|
+
`smbclient` oder dem Windows-Explorer problemlos erreichbar war.
|
|
16
|
+
|
|
17
|
+
**Ursache:** `buildClient(config)` speichert `config.base_path` intern als
|
|
18
|
+
`client.basePath`. Der Wrapper-`resolvePath(rel)` baut daraus
|
|
19
|
+
`<share>/<basePath>/<rel>`. Die Test-Route rief aber
|
|
20
|
+
`client.readdir(testCfg.base_path)` auf und übergab den Basispfad **noch
|
|
21
|
+
einmal** als relatives Argument. Ergebnis: aus `base_path = "static"` wurde
|
|
22
|
+
auf der Leitung `buero/static/static` — den es natürlich nicht gibt. Die
|
|
23
|
+
Diagnose-Ausgabe (`tested_path: static/static`) zeigte den Bug bereits
|
|
24
|
+
klar an, wurde aber bisher als Symptom statt Ursache gelesen.
|
|
25
|
+
|
|
26
|
+
**Fix:** Die Test-Route ruft jetzt `client.readdir("")` auf. Der Wrapper
|
|
27
|
+
resolvet das intern korrekt zu `<share>/<basePath>` — also genau dem Pfad,
|
|
28
|
+
den der Benutzer im Formular eingetragen hat. Für Anzeige-Zwecke (Meldungen,
|
|
29
|
+
Diagnose-Kacheln, Fallback-Probes) wird der Basispfad separat in
|
|
30
|
+
`baseForDisplay` gehalten.
|
|
31
|
+
|
|
32
|
+
### Added – **Standalone-Diagnose-Skript `tools/diag-basepath.js`**
|
|
33
|
+
|
|
34
|
+
Wer den Verbindungsproblemen auf den Grund gehen will, kann jetzt außerhalb
|
|
35
|
+
von Saltcorn probieren, was smb3-client tatsächlich sieht:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
node tools/diag-basepath.js \
|
|
39
|
+
--host 192.168.110.10 --share buero --path static \
|
|
40
|
+
--user 01_vassen --domain buero.ib-vassen.de --password ...
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Das Skript führt sechs Probes durch (Share-Root, Share-Root mit Slash,
|
|
44
|
+
`stat` auf Ziel, `readdir` auf Ziel, Groß-/Kleinschreibvariante) und
|
|
45
|
+
druckt für jede den NT-Status-Code — hilfreich beim Aufspüren von
|
|
46
|
+
Schreibvarianten oder ACL-Problemen.
|
|
47
|
+
|
|
48
|
+
## [0.4.7] – 2026-07-05
|
|
49
|
+
|
|
50
|
+
### Fixed – **Irreführende Windows-UNC-Anzeige in der Fehlermeldung**
|
|
51
|
+
|
|
52
|
+
Die v0.4.6-Fehlermeldung zeigte den getesteten Pfad als Windows-UNC
|
|
53
|
+
(`\\\\192.168.110.10\\buero\\static`). Das war irreführend, weil das
|
|
54
|
+
Plugin an smb3-client tatsächlich Forward-Slash-Pfade schickt
|
|
55
|
+
(`buero/static`) — die Backslash-Anzeige suggerierte einen Bug, der
|
|
56
|
+
keiner ist. Jetzt wird der Pfad in genau der Form gezeigt, die auch
|
|
57
|
+
tatsächlich über die Leitung geht: `<share>/<basispfad>`.
|
|
58
|
+
|
|
59
|
+
## [0.4.6] – 2026-07-05
|
|
60
|
+
|
|
61
|
+
### Fixed – **Schreibvarianten-Test verdoppelte den Basispfad**
|
|
62
|
+
|
|
63
|
+
Die Case-Probes in v0.4.4/0.4.5 riefen `client.readdir(<candidate>)` auf.
|
|
64
|
+
Der Wrapper prependiert aber automatisch den bereits konfigurierten
|
|
65
|
+
`basePath` — dadurch wurde intern nach `buero/static/static` statt
|
|
66
|
+
`buero/static` gesucht. Alle Probes scheiterten deshalb systematisch
|
|
67
|
+
mit derselben Meldung, unabhängig von der wahren Ursache.
|
|
68
|
+
|
|
69
|
+
**Fix:** Die Probes gehen jetzt direkt gegen den rohen `smb3-client`
|
|
70
|
+
(`client._raw.readdir(share/<absoluter Pfad>)`) und bauen die
|
|
71
|
+
From-Share-Root-Pfade selbst zusammen. Der Test-Endpunkt zeigt jetzt
|
|
72
|
+
zusätzlich pro Probe den tatsächlich getesteten Pfad an.
|
|
73
|
+
|
|
74
|
+
### Fixed – **Falscher „Login rejected“-Hinweis bei BASE_PATH_NOT_FOUND**
|
|
75
|
+
|
|
76
|
+
Der Fallback-Hint-Table prüfte per Substring-Match auf `access_denied`
|
|
77
|
+
u.\u00e4. — das triggerte auf den erklärenden Text in unserer eigenen
|
|
78
|
+
deutschen Fehlermeldung (wir *erwähnen* `ACCESS_DENIED` didaktisch).
|
|
79
|
+
Ein ungefundener Basispfad wurde deshalb zusätzlich mit dem irreführenden
|
|
80
|
+
Hinweis „Login rejected“ versehen.
|
|
81
|
+
|
|
82
|
+
**Fix:** Fehler mit eigenem `code` (`BASE_PATH_NOT_FOUND`,
|
|
83
|
+
`BASE_PATH_NOT_A_DIR`) überspringen den Substring-Match — die
|
|
84
|
+
Wrapper-Meldung ist bereits vollständig und braucht keinen „Hint“.
|
|
85
|
+
|
|
86
|
+
### Changed – **Bessere Basispfad-Meldung**
|
|
87
|
+
|
|
88
|
+
Wenn kein Case-Match gefunden wurde, zeigt die Meldung jetzt den
|
|
89
|
+
tatsächlich getesteten UNC-Pfad (`\\\\server\\share\\pfad`) und weist
|
|
90
|
+
explizit darauf hin, dass der Basispfad **relativ zur Freigabe** ist —
|
|
91
|
+
also `unterordner` und **nicht** `sharename/unterordner`.
|
|
92
|
+
|
|
7
93
|
## [0.4.5] – 2026-07-05
|
|
8
94
|
|
|
9
95
|
### Fixed – **Test-Verbindung-Button reagiert nicht mehr (v0.4.4-Regression)**
|
package/index.js
CHANGED
|
@@ -853,72 +853,73 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
|
|
|
853
853
|
// to stat("") — that already proves the whole handshake
|
|
854
854
|
// (TCP + Negotiate + Session + TREE_CONNECT + Auth) works, which
|
|
855
855
|
// is all the connection test actually promises.
|
|
856
|
-
|
|
857
|
-
//
|
|
858
|
-
//
|
|
859
|
-
//
|
|
860
|
-
//
|
|
861
|
-
//
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
// for directories with strict ACLs. readdir() sends
|
|
865
|
-
// createOptions=1 (DIRECTORY_FILE) and is the right primitive
|
|
866
|
-
// for a base_path check anyway — we want to know the folder
|
|
867
|
-
// can actually be listed, not just opened.
|
|
856
|
+
//
|
|
857
|
+
// IMPORTANT: buildClient already stores `base_path` as the
|
|
858
|
+
// wrapper's `basePath`, and wrapper.readdir(x) resolves to
|
|
859
|
+
// `share/basePath/x`. So we call readdir("") to list the
|
|
860
|
+
// configured basePath itself — not readdir(base_path), which
|
|
861
|
+
// would append it twice (bug fixed in 0.4.8).
|
|
862
|
+
const rel = "";
|
|
863
|
+
const baseForDisplay = testCfg.base_path ? sanitizeRelativePath(testCfg.base_path) : "";
|
|
868
864
|
try {
|
|
869
865
|
return await client.readdir(rel);
|
|
870
866
|
} catch (err) {
|
|
871
867
|
// Look at both the wrapper error and its underlying cause.
|
|
872
868
|
const causeMsg = String((err && err.cause && err.cause.message) || "");
|
|
873
869
|
const msg = String((err && err.message) || err || "") + " " + causeMsg;
|
|
874
|
-
|
|
870
|
+
// The path we effectively asked the server to enumerate is
|
|
871
|
+
// `share/basePath` (rel is always "" here — basePath is the
|
|
872
|
+
// subdirectory to test).
|
|
873
|
+
const effectivePath = testCfg.share + (baseForDisplay ? "/" + baseForDisplay : "");
|
|
874
|
+
const isRootProbe = !baseForDisplay;
|
|
875
875
|
const isRootEnumBug = /0xC0000033|OBJECT_NAME_INVALID|Share-Root/i.test(msg);
|
|
876
876
|
if (isRootProbe && isRootEnumBug) {
|
|
877
|
-
//
|
|
878
|
-
//
|
|
879
|
-
//
|
|
877
|
+
// Share-root enumeration is a known smb3-client-vs-Samba
|
|
878
|
+
// limitation. Fall back to stat("") which proves the whole
|
|
879
|
+
// handshake works even if we can't list the root.
|
|
880
880
|
try {
|
|
881
881
|
await client.stat("");
|
|
882
|
-
// Signal to the caller that the connection works, but
|
|
883
|
-
// the share root cannot be enumerated on this server.
|
|
884
882
|
const marker = [];
|
|
885
883
|
marker._rootNotEnumerable = true;
|
|
886
884
|
return marker;
|
|
887
885
|
} catch (statErr) {
|
|
888
|
-
// stat also failed — surface the original error.
|
|
889
886
|
throw err;
|
|
890
887
|
}
|
|
891
888
|
}
|
|
892
|
-
//
|
|
893
|
-
//
|
|
894
|
-
//
|
|
895
|
-
// (if not the share root) and probe common alternative
|
|
896
|
-
// spellings of the missing segment so we can distinguish
|
|
897
|
-
// typo / case-mismatch / permission problems.
|
|
889
|
+
// NAME_NOT_FOUND / PATH_NOT_FOUND on a configured base_path.
|
|
890
|
+
// Gather diagnostics: raw probes against alternate spellings
|
|
891
|
+
// and (if possible) a listing of the parent directory.
|
|
898
892
|
const isMissing = /OBJECT_NAME_NOT_FOUND|OBJECT_PATH_NOT_FOUND|ENOENT|STATUS_NO_SUCH_FILE|existiert.*nicht/i.test(msg);
|
|
899
|
-
if (
|
|
900
|
-
const parts =
|
|
893
|
+
if (baseForDisplay && isMissing) {
|
|
894
|
+
const parts = baseForDisplay.split("/").filter(Boolean);
|
|
901
895
|
const missing = parts[parts.length - 1];
|
|
902
896
|
const parent = parts.slice(0, -1).join("/");
|
|
903
897
|
const parentAbs = parent || "(Share-Root)";
|
|
898
|
+
// Probe via the raw smb3-client so we bypass the wrapper's
|
|
899
|
+
// basePath prefix and can test siblings/alternate spellings
|
|
900
|
+
// directly from the share root.
|
|
901
|
+
const raw = client._raw;
|
|
902
|
+
const rawShare = client.shareName;
|
|
903
|
+
async function rawReaddir(relFromShareRoot) {
|
|
904
|
+
const full = relFromShareRoot ? rawShare + "/" + relFromShareRoot : rawShare;
|
|
905
|
+
return await raw.readdir(full, { withFileTypes: true });
|
|
906
|
+
}
|
|
904
907
|
let siblings = null;
|
|
905
908
|
let parent_error = null;
|
|
906
909
|
if (parent) {
|
|
907
910
|
try {
|
|
908
|
-
const listing = await
|
|
911
|
+
const listing = await rawReaddir(parent);
|
|
909
912
|
siblings = Array.isArray(listing)
|
|
910
913
|
? listing.map((d) => ({
|
|
911
|
-
name: d &&
|
|
912
|
-
isDirectory: !!(d &&
|
|
914
|
+
name: d && d.name,
|
|
915
|
+
isDirectory: !!(d && typeof d.isDirectory === "function" && d.isDirectory()),
|
|
913
916
|
}))
|
|
914
917
|
: null;
|
|
915
918
|
} catch (parentErr) {
|
|
916
919
|
parent_error = String((parentErr && parentErr.message) || parentErr || "");
|
|
917
920
|
}
|
|
918
921
|
}
|
|
919
|
-
// Probe alternate spellings
|
|
920
|
-
// This lets us report "Ordner existiert unter anderem Namen" if
|
|
921
|
-
// Samba is running with case sensitive = yes / case-preserved.
|
|
922
|
+
// Probe alternate spellings of the missing segment.
|
|
922
923
|
const probes = [];
|
|
923
924
|
const seen = new Set();
|
|
924
925
|
const addProbe = (name) => {
|
|
@@ -932,16 +933,18 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
|
|
|
932
933
|
addProbe(missing.charAt(0).toUpperCase() + missing.slice(1).toLowerCase());
|
|
933
934
|
const probe_results = [];
|
|
934
935
|
for (const p of probes) {
|
|
935
|
-
|
|
936
|
+
// From-share-root path: `parent/candidate`. NEVER include
|
|
937
|
+
// client.basePath here — the raw client is share-relative.
|
|
938
|
+
const relFromShare = [parent, p].filter(Boolean).join("/");
|
|
936
939
|
let ok = false;
|
|
937
940
|
let perr = null;
|
|
938
941
|
try {
|
|
939
|
-
await
|
|
942
|
+
await rawReaddir(relFromShare);
|
|
940
943
|
ok = true;
|
|
941
944
|
} catch (pErr) {
|
|
942
945
|
perr = String((pErr && pErr.message) || pErr || "");
|
|
943
946
|
}
|
|
944
|
-
probe_results.push({ candidate: p, ok, error: ok ? null : perr });
|
|
947
|
+
probe_results.push({ candidate: p, tested_path: relFromShare, ok, error: ok ? null : perr });
|
|
945
948
|
}
|
|
946
949
|
const workingAlt = probe_results.find(
|
|
947
950
|
(r) => r.ok && r.candidate !== missing
|
|
@@ -949,25 +952,24 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
|
|
|
949
952
|
let hintText;
|
|
950
953
|
if (workingAlt) {
|
|
951
954
|
hintText =
|
|
952
|
-
"Der Ordner
|
|
955
|
+
"Der Ordner hei\u00dft auf dem Server \u201e" +
|
|
953
956
|
workingAlt.candidate +
|
|
954
|
-
"\u201c (andere
|
|
957
|
+
"\u201c (andere Gro\u00df-/Kleinschreibung). Bitte den " +
|
|
955
958
|
"Basispfad exakt so eintragen \u2014 der Samba-Server ist " +
|
|
956
959
|
"case-sensitive (\u201ecase sensitive = yes\u201c in smb.conf).";
|
|
957
960
|
} else {
|
|
958
961
|
hintText =
|
|
959
|
-
"Der Basispfad \u201e" +
|
|
960
|
-
"
|
|
961
|
-
"
|
|
962
|
-
"
|
|
963
|
-
"
|
|
962
|
+
"Der Basispfad \u201e" + baseForDisplay + "\u201c wurde auf der " +
|
|
963
|
+
"Freigabe \u201e" + testCfg.share + "\u201c nicht " +
|
|
964
|
+
"gefunden. Intern getestet wurde: \u201e" + effectivePath +
|
|
965
|
+
"\u201c. M\u00f6gliche Ursachen: (a) Ordner existiert unter " +
|
|
966
|
+
"genau diesem Namen nicht in dieser Freigabe (Basispfad ist " +
|
|
967
|
+
"relativ zur Freigabe \u2014 also z.\u202fB. \u201eunterordner\u201c, " +
|
|
968
|
+
"nicht \u201e" + testCfg.share + "/unterordner\u201c); " +
|
|
964
969
|
"(b) der angemeldete Benutzer \u201e" +
|
|
965
970
|
(testCfg.username || "(anonymous)") +
|
|
966
|
-
"\u201c
|
|
967
|
-
"
|
|
968
|
-
"NAME_NOT_FOUND statt ACCESS_DENIED); " +
|
|
969
|
-
"(c) der Ordner ist per \u201eveto files\u201c / " +
|
|
970
|
-
"\u201ehide files\u201c auf dem Server ausgeblendet.";
|
|
971
|
+
"\u201c darf den Ordner nicht sehen; " +
|
|
972
|
+
"(c) der Ordner ist per Samba-Konfiguration ausgeblendet.";
|
|
971
973
|
}
|
|
972
974
|
const e = new Error(hintText);
|
|
973
975
|
e.cause = err;
|
|
@@ -1017,7 +1019,19 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
|
|
|
1017
1019
|
const code = (err && (err.code || err.errno)) || null;
|
|
1018
1020
|
let hint = null;
|
|
1019
1021
|
const m = msg.toLowerCase();
|
|
1020
|
-
|
|
1022
|
+
// If the wrapper already produced a well-formed, self-explanatory
|
|
1023
|
+
// error (own `code` set), skip the substring-match hint table —
|
|
1024
|
+
// it would otherwise trigger on words that appear inside our own
|
|
1025
|
+
// human-readable German explanation text (e.g. our message text
|
|
1026
|
+
// mentions "access_denied" or "path" for teaching purposes).
|
|
1027
|
+
const codesWithOwnMessage = new Set([
|
|
1028
|
+
"BASE_PATH_NOT_FOUND",
|
|
1029
|
+
"BASE_PATH_NOT_A_DIR",
|
|
1030
|
+
]);
|
|
1031
|
+
if (code && codesWithOwnMessage.has(code)) {
|
|
1032
|
+
hint = null;
|
|
1033
|
+
}
|
|
1034
|
+
else if (code === "ECONNREFUSED" || m.includes("econnrefused"))
|
|
1021
1035
|
hint = "The server refused the connection on port " + testCfg.port +
|
|
1022
1036
|
". Check that Samba is running and that a firewall (or Docker) does not block TCP/445.";
|
|
1023
1037
|
else if (code === "ETIMEDOUT" || m.includes("etimedout") || m.includes("timed out"))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "saltcorn-samba",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
4
4
|
"description": "Saltcorn plugin: browse, upload, rename and delete files on a Samba/CIFS share via SMB 3.1.1 (AES-CMAC signing, optional encryption). File-manager view, directory tree, inline PDF viewer, external-app open (smb://).",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|