saltcorn-samba 0.4.6 → 0.4.9

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/index.js +61 -51
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,88 @@ 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.9] – 2026-07-05
8
+
9
+ ### Fixed – **`0xC0000033` beim Auflisten des Basispfads (smb3-client-vs-Samba-4.23-Bug jetzt auch für Basispfade abgefangen)**
10
+
11
+ Auch in 0.4.8 lief der Verbindungstest mit gesetztem Basispfad in
12
+ `QUERY_DIRECTORY failed: 0xC0000033` (`OBJECT_NAME_INVALID`). Ohne
13
+ Basispfad war die Verbindung korrekt und zeigte den gelben Hinweis.
14
+
15
+ **Ursache:** `smb3-client` sendet `QUERY_DIRECTORY` mit hart kodiertem
16
+ `FileInformationClass = 37` (`FileIdBothDirectoryInformation`). Samba
17
+ 4.23 lehnt das für bestimmte Verzeichnisse ab — auch dann, wenn der
18
+ Ordner existiert, geöffnet und `stat`-bar ist. Genau derselbe Bug, der
19
+ bisher nur beim Auflisten des Share-Roots als ‚Root nicht auflistbar'
20
+ aufgefangen wurde, kann auch den konfigurierten Basispfad selbst
21
+ treffen.
22
+
23
+ **Fix:** Der Fallback greift jetzt auch für Basispfade. Wenn
24
+ `client.readdir("")` mit `0xC0000033` scheitert, versucht die Test-Route
25
+ zusätzlich `client.stat("")` auf denselben Pfad:
26
+
27
+ - **stat OK** → Verbindung + Basispfad bestätigt, gelber Hinweis mit
28
+ Erklärung des bekannten Bugs und Workaround-Vorschlägen.
29
+ - **stat schlägt auch fehl** → normaler Fehlerpfad (Basispfad-
30
+ Diagnose mit Schreibvarianten-Probes) läuft weiter wie bisher.
31
+
32
+ Damit lassen sich Basispfade eintragen, auf die `readdir` per
33
+ `FileIdBothDirectoryInformation` scheitert — der Download und die
34
+ Unterordner-Ansicht funktionieren über die normalen SMB-Calls
35
+ trotzdem.
36
+
37
+ ## [0.4.8] – 2026-07-05
38
+
39
+ ### Fixed – **Basispfad wurde beim Verbindungstest doppelt vorangestellt (die eigentliche Ursache)**
40
+
41
+ In allen Vorgängerversionen (0.4.4 – 0.4.7) hat der Verbindungstest den
42
+ konfigurierten Basispfad **zweimal** auf den Server geschickt und deshalb
43
+ systematisch die Meldung *„Der Basispfad ist auf dem Server nicht auffindbar"*
44
+ produziert — auch dann, wenn der Pfad in Wirklichkeit existierte und mit
45
+ `smbclient` oder dem Windows-Explorer problemlos erreichbar war.
46
+
47
+ **Ursache:** `buildClient(config)` speichert `config.base_path` intern als
48
+ `client.basePath`. Der Wrapper-`resolvePath(rel)` baut daraus
49
+ `<share>/<basePath>/<rel>`. Die Test-Route rief aber
50
+ `client.readdir(testCfg.base_path)` auf und übergab den Basispfad **noch
51
+ einmal** als relatives Argument. Ergebnis: aus `base_path = "static"` wurde
52
+ auf der Leitung `buero/static/static` — den es natürlich nicht gibt. Die
53
+ Diagnose-Ausgabe (`tested_path: static/static`) zeigte den Bug bereits
54
+ klar an, wurde aber bisher als Symptom statt Ursache gelesen.
55
+
56
+ **Fix:** Die Test-Route ruft jetzt `client.readdir("")` auf. Der Wrapper
57
+ resolvet das intern korrekt zu `<share>/<basePath>` — also genau dem Pfad,
58
+ den der Benutzer im Formular eingetragen hat. Für Anzeige-Zwecke (Meldungen,
59
+ Diagnose-Kacheln, Fallback-Probes) wird der Basispfad separat in
60
+ `baseForDisplay` gehalten.
61
+
62
+ ### Added – **Standalone-Diagnose-Skript `tools/diag-basepath.js`**
63
+
64
+ Wer den Verbindungsproblemen auf den Grund gehen will, kann jetzt außerhalb
65
+ von Saltcorn probieren, was smb3-client tatsächlich sieht:
66
+
67
+ ```bash
68
+ node tools/diag-basepath.js \
69
+ --host 192.168.110.10 --share buero --path static \
70
+ --user 01_vassen --domain buero.ib-vassen.de --password ...
71
+ ```
72
+
73
+ Das Skript führt sechs Probes durch (Share-Root, Share-Root mit Slash,
74
+ `stat` auf Ziel, `readdir` auf Ziel, Groß-/Kleinschreibvariante) und
75
+ druckt für jede den NT-Status-Code — hilfreich beim Aufspüren von
76
+ Schreibvarianten oder ACL-Problemen.
77
+
78
+ ## [0.4.7] – 2026-07-05
79
+
80
+ ### Fixed – **Irreführende Windows-UNC-Anzeige in der Fehlermeldung**
81
+
82
+ Die v0.4.6-Fehlermeldung zeigte den getesteten Pfad als Windows-UNC
83
+ (`\\\\192.168.110.10\\buero\\static`). Das war irreführend, weil das
84
+ Plugin an smb3-client tatsächlich Forward-Slash-Pfade schickt
85
+ (`buero/static`) — die Backslash-Anzeige suggerierte einen Bug, der
86
+ keiner ist. Jetzt wird der Pfad in genau der Form gezeigt, die auch
87
+ tatsächlich über die Leitung geht: `<share>/<basispfad>`.
88
+
7
89
  ## [0.4.6] – 2026-07-05
8
90
 
9
91
  ### Fixed – **Schreibvarianten-Test verdoppelte den Basispfad**
package/index.js CHANGED
@@ -853,72 +853,74 @@ 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
- const rel = testCfg.base_path ? sanitizeRelativePath(testCfg.base_path) : "";
857
- // When a base_path is set, verify it exists and is a directory
858
- // BEFORE trying to enumerate it. This turns the opaque
859
- // "CREATE failed: STATUS_OBJECT_NAME_NOT_FOUND" into a clear
860
- // "Basispfad existiert nicht" hint the user can act on.
861
- // We used to run a stat() first. That turned out to be flaky:
862
- // smb3-client's stat() sends a CREATE with createOptions=0
863
- // (no directory hint) which some Samba configurations reject
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
- const isRootProbe = !rel;
875
- const isRootEnumBug = /0xC0000033|OBJECT_NAME_INVALID|Share-Root/i.test(msg);
876
- if (isRootProbe && isRootEnumBug) {
877
- // Fall back: proof-of-life via stat on share root. This
878
- // confirms TCP + Negotiate + Session + Auth + TREE_CONNECT
879
- // without hitting the broken QUERY_DIRECTORY path.
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
+ // smb3-client hardcodes FileInformationClass=37
876
+ // (FileIdBothDirectoryInformation) in QUERY_DIRECTORY. Some
877
+ // Samba 4.23+ configurations reject that with 0xC0000033
878
+ // (OBJECT_NAME_INVALID) for specific directories — even though
879
+ // the same directory can be opened, stat'd and traversed. In
880
+ // that case we fall back to a plain stat: if the folder exists
881
+ // (stat succeeds) we can prove the connection works, but flag
882
+ // that enumeration is broken so the caller shows the yellow
883
+ // hint instead of a red error.
884
+ const isEnumBug = /0xC0000033|OBJECT_NAME_INVALID|QUERY_DIRECTORY failed/i.test(msg);
885
+ if (isEnumBug) {
880
886
  try {
881
887
  await client.stat("");
882
- // Signal to the caller that the connection works, but
883
- // the share root cannot be enumerated on this server.
884
888
  const marker = [];
885
- marker._rootNotEnumerable = true;
889
+ if (isRootProbe) {
890
+ marker._rootNotEnumerable = true;
891
+ } else {
892
+ marker._basePathNotEnumerable = true;
893
+ marker._basePathForDisplay = baseForDisplay;
894
+ }
886
895
  return marker;
887
896
  } catch (statErr) {
888
- // stat also failed — surface the original error.
889
- throw err;
897
+ // stat also failed — fall through to normal error handling
898
+ // (missing-basepath diagnostics or generic rethrow).
890
899
  }
891
900
  }
892
- // A non-existent base_path (or one hidden from this user by
893
- // Samba's "hide unreadable" behaviour) surfaces here. Try to
894
- // gather actionable diagnostics: list the parent directory
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.
901
+ // NAME_NOT_FOUND / PATH_NOT_FOUND on a configured base_path.
902
+ // Gather diagnostics: raw probes against alternate spellings
903
+ // and (if possible) a listing of the parent directory.
898
904
  const isMissing = /OBJECT_NAME_NOT_FOUND|OBJECT_PATH_NOT_FOUND|ENOENT|STATUS_NO_SUCH_FILE|existiert.*nicht/i.test(msg);
899
- if (rel && isMissing) {
900
- const parts = rel.split("/").filter(Boolean);
905
+ if (baseForDisplay && isMissing) {
906
+ const parts = baseForDisplay.split("/").filter(Boolean);
901
907
  const missing = parts[parts.length - 1];
902
908
  const parent = parts.slice(0, -1).join("/");
903
909
  const parentAbs = parent || "(Share-Root)";
904
- // We must probe via the *raw* smb3-client because the
905
- // wrapper's readdir() automatically prepends basePath
906
- // that would double up the segment we're trying to test
907
- // (e.g. readdir("static") → "buero/static/static").
910
+ // Probe via the raw smb3-client so we bypass the wrapper's
911
+ // basePath prefix and can test siblings/alternate spellings
912
+ // directly from the share root.
908
913
  const raw = client._raw;
909
914
  const rawShare = client.shareName;
910
915
  async function rawReaddir(relFromShareRoot) {
911
- const full = [rawShare, relFromShareRoot].filter(Boolean).join("/");
916
+ const full = relFromShareRoot ? rawShare + "/" + relFromShareRoot : rawShare;
912
917
  return await raw.readdir(full, { withFileTypes: true });
913
918
  }
914
919
  let siblings = null;
915
920
  let parent_error = null;
916
921
  if (parent) {
917
922
  try {
918
- // parent here is relative to basePath — but we want
919
- // absolute-from-share-root paths in the raw call.
920
- const parentFromShare = [client.basePath, parent].filter(Boolean).join("/");
921
- const listing = await rawReaddir(parentFromShare);
923
+ const listing = await rawReaddir(parent);
922
924
  siblings = Array.isArray(listing)
923
925
  ? listing.map((d) => ({
924
926
  name: d && d.name,
@@ -929,9 +931,7 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
929
931
  parent_error = String((parentErr && parentErr.message) || parentErr || "");
930
932
  }
931
933
  }
932
- // Probe alternate spellings: original, upper, lower, capitalised.
933
- // This lets us report "Ordner existiert unter anderem Namen" if
934
- // Samba is running with case sensitive = yes / case-preserved.
934
+ // Probe alternate spellings of the missing segment.
935
935
  const probes = [];
936
936
  const seen = new Set();
937
937
  const addProbe = (name) => {
@@ -945,9 +945,9 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
945
945
  addProbe(missing.charAt(0).toUpperCase() + missing.slice(1).toLowerCase());
946
946
  const probe_results = [];
947
947
  for (const p of probes) {
948
- // Build the *from-share-root* relative path so the probe
949
- // does not accidentally get basePath prepended twice.
950
- const relFromShare = [client.basePath, parent, p].filter(Boolean).join("/");
948
+ // From-share-root path: `parent/candidate`. NEVER include
949
+ // client.basePath here the raw client is share-relative.
950
+ const relFromShare = [parent, p].filter(Boolean).join("/");
951
951
  let ok = false;
952
952
  let perr = null;
953
953
  try {
@@ -961,7 +961,6 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
961
961
  const workingAlt = probe_results.find(
962
962
  (r) => r.ok && r.candidate !== missing
963
963
  );
964
- const testedPath = "\\\\" + testCfg.server + "\\" + testCfg.share + "\\" + rel.replace(/\//g, "\\");
965
964
  let hintText;
966
965
  if (workingAlt) {
967
966
  hintText =
@@ -972,11 +971,11 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
972
971
  "case-sensitive (\u201ecase sensitive = yes\u201c in smb.conf).";
973
972
  } else {
974
973
  hintText =
975
- "Der Basispfad \u201e" + rel + "\u201c wurde auf der " +
974
+ "Der Basispfad \u201e" + baseForDisplay + "\u201c wurde auf der " +
976
975
  "Freigabe \u201e" + testCfg.share + "\u201c nicht " +
977
- "gefunden. Getestet wurde der Pfad: " + testedPath + ". " +
978
- "M\u00f6gliche Ursachen: (a) Ordner existiert unter genau " +
979
- "diesem Namen nicht in dieser Freigabe (Basispfad ist " +
976
+ "gefunden. Intern getestet wurde: \u201e" + effectivePath +
977
+ "\u201c. M\u00f6gliche Ursachen: (a) Ordner existiert unter " +
978
+ "genau diesem Namen nicht in dieser Freigabe (Basispfad ist " +
980
979
  "relativ zur Freigabe \u2014 also z.\u202fB. \u201eunterordner\u201c, " +
981
980
  "nicht \u201e" + testCfg.share + "/unterordner\u201c); " +
982
981
  "(b) der angemeldete Benutzer \u201e" +
@@ -1003,6 +1002,7 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
1003
1002
  });
1004
1003
  const took = Date.now() - started;
1005
1004
  const rootNotEnum = Array.isArray(listing) && listing._rootNotEnumerable === true;
1005
+ const baseNotEnum = Array.isArray(listing) && listing._basePathNotEnumerable === true;
1006
1006
  return res.json({
1007
1007
  ok: true,
1008
1008
  server: testCfg.server,
@@ -1024,6 +1024,16 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
1024
1024
  "Verhalten mit smb3-client). Bitte setzen Sie einen Basispfad " +
1025
1025
  "in der Plugin-Config (z.\u202fB. einen Unterordner der " +
1026
1026
  "Freigabe) — dann funktioniert der File-Manager vollständig."
1027
+ : baseNotEnum
1028
+ ? "Verbindung + Anmeldung erfolgreich, Basispfad \u201e" +
1029
+ (listing._basePathForDisplay || testCfg.base_path || "") +
1030
+ "\u201c wurde vom Server best\u00e4tigt (stat OK). Das direkte " +
1031
+ "Auflisten dieses Ordners lehnt Samba 4.23 mit smb3-client " +
1032
+ "aktuell ab (bekannter Bug: FileInformationClass=37 hart " +
1033
+ "kodiert, siehe README). Ordner-Unterb\u00e4ume, Datei-Downloads " +
1034
+ "und PDF-Ansicht funktionieren \u2014 nur die Wurzel des " +
1035
+ "Basispfads kann nicht direkt aufgelistet werden. Workaround: " +
1036
+ "tieferen Basispfad setzen oder Unterordner ansprechen."
1027
1037
  : undefined,
1028
1038
  });
1029
1039
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "saltcorn-samba",
3
- "version": "0.4.6",
3
+ "version": "0.4.9",
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": {