saltcorn-samba 0.4.0 → 0.4.2

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 CHANGED
@@ -4,6 +4,85 @@ 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.2] – 2026-07-05
8
+
9
+ ### Fixed – **Dropdown-Felder zeigen `[object Object]` statt Optionen**
10
+
11
+ Die neuen Felder `signing_mode` und `encryption_mode` wurden in der
12
+ Saltcorn-UI mit `[object Object]` als einzige Auswahl gerendert.
13
+
14
+ **Ursache:** In der genutzten Saltcorn-Version werden `attributes.options`
15
+ als einfaches String-Array erwartet (`["a", "b", "c"]`), nicht als
16
+ `{value, label}`-Objekt. Die Objekt-Form ist erst in neueren Builds
17
+ vollständig unterstützt; sonst castet das UI die Objekte zu Strings.
18
+
19
+ **Fix:** `options` auf String-Array umgestellt —
20
+ `["if-offered", "required", "disabled"]`. Die Beschriftung bleibt in
21
+ der `sublabel` erhalten (der Config-Wizard erklärt jeden Wert dort).
22
+
23
+ ### Fixed – **`STATUS_OBJECT_NAME_NOT_FOUND` durch fehlgeleiteten Root-Fallback**
24
+
25
+ Der 0.4.1-Fallback `share/.` triggerte auf Samba einen anderen NT-Status:
26
+ `STATUS_OBJECT_NAME_NOT_FOUND` (0xC0000034 / `ENOENT`), weil Samba `.` als
27
+ literalen Dateinamen sucht statt als Current-Directory-Marker (das ist im
28
+ POSIX-Layer, nicht im SMB2-Protokoll). Der zweite Fallback `share/*` ist
29
+ protokoll-illegal (Wildcard im CREATE) und wird ebenfalls abgelehnt.
30
+
31
+ **Neuer Ansatz:** Wir versuchen den Fallback erst gar nicht. Statt zu
32
+ raten geben wir eine klare deutsche Fehlermeldung aus: **einen Basispfad
33
+ setzen**. Das ist die einzige zuverlässige Lösung, solange `smb3-client`
34
+ die `FileInformationClass` nicht konfigurierbar macht.
35
+
36
+ Die Test-Route bleibt weiterhin nachsichtig: bei `OBJECT_NAME_INVALID`
37
+ auf dem Root fällt sie auf `stat("")` zurück und meldet ein Erfolg mit
38
+ Hinweis („Verbindung + Anmeldung erfolgreich, aber Share-Root nicht
39
+ direkt auflistbar — bitte Basispfad setzen“). Der Hinweis wird als gelbe
40
+ Box im Test-Ergebnis angezeigt.
41
+
42
+ ## [0.4.1] – 2026-07-05
43
+
44
+ ### Fixed – **`QUERY_DIRECTORY failed: 0xC0000033` auf Share-Root (Samba 4.20+/4.23+)**
45
+
46
+ Symptom nach dem 0.4.0-Update: der Verbindungstest schlägt sofort fehl mit
47
+
48
+ ```
49
+ Fehler: QUERY_DIRECTORY failed: 0xC0000033
50
+ ```
51
+
52
+ (`STATUS_OBJECT_NAME_INVALID`). Betroffen sind Setups **ohne Basispfad** —
53
+ sobald ein Basispfad gesetzt ist, greift der Fehler nicht.
54
+
55
+ **Ursache:** `smb3-client` öffnet den Share-Root im SMB2-CREATE mit einem
56
+ leeren Filename (`""`). Moderne Samba-Versionen (bestehen jedenfalls
57
+ **4.20+**, bestätigt auf **4.23.9**) akzeptieren das für CREATE, weisen
58
+ aber das anschließende `QUERY_DIRECTORY` mit `FileIdBothDirectoryInformation`
59
+ auf dem leeren Namen als `OBJECT_NAME_INVALID` zurück. Vergleichbare
60
+ Probleme sind aus anderen Java-/Go-SMB-Client-Bibliotheken bekannt (z. B.
61
+ [smbj#80](https://github.com/hierynomus/smbj/issues/80)).
62
+
63
+ **Lösung:**
64
+
65
+ 1. `smb-client.js#readdir("")` bekommt einen Fallback: wenn der
66
+ Server auf dem leeren Root mit `OBJECT_NAME_INVALID` antwortet, wird
67
+ die Auflistung noch einmal mit `share/.` (aktuelles Verzeichnis) und,
68
+ falls das ebenfalls scheitert, mit `share/*` (Wildcard) probiert.
69
+ Erst wenn auch das nicht klappt, wird eine deutsche Fehlermeldung mit
70
+ Handlungsanweisung ausgelöst.
71
+ 2. Die `/sambatest`-Route greift auf `client.stat("")` zurück, wenn das
72
+ Root-`readdir` mit `OBJECT_NAME_INVALID` scheitert. `stat("")`
73
+ verwendet CREATE ohne `DIRECTORY_FILE`-Flag und ohne QUERY_DIRECTORY —
74
+ das beweist Netzwerk + Negotiate + Session-Setup + TREE_CONNECT + Auth
75
+ ohne die problematische Query. Der Test liefert dann `entry_count: 0`
76
+ und den Hinweis, dass ein Basispfad gesetzt werden sollte, sofern der
77
+ Server das Root-Listing nicht anders bereitstellt.
78
+
79
+ ### Empfehlung
80
+
81
+ Wenn Ihr Samba-Server das Share-Root-`QUERY_DIRECTORY` weiterhin ablehnt,
82
+ setzen Sie in der Plugin-Config einen **Basispfad** (z. B. `daten` oder
83
+ `projekte`) — dann sind alle Directory-Listings innerhalb dieses
84
+ Unterverzeichnisses, was durchgängig funktioniert.
85
+
7
86
  ## [0.4.0] – 2026-07-05
8
87
 
9
88
  ### ⚠️ BREAKING CHANGES
package/index.js CHANGED
@@ -160,6 +160,9 @@ window.sambaTestConn = async function(btn) {
160
160
  var rows = (data.entries||[]).map(function(e){
161
161
  return '<li>'+ (e.isDirectory?'📁 ':'📄 ') + String(e.name).replace(/[<>&]/g,'?') +'</li>';
162
162
  }).join('');
163
+ var noteHtml = data.note
164
+ ? '<div style="margin-top:.5rem;padding:.4rem .6rem;background:#fff3cd;border:1px solid #ffeeba;border-radius:.25rem"><b>Hinweis:</b> ' + String(data.note).replace(/[<>&]/g,'?') + '</div>'
165
+ : '';
163
166
  out.innerHTML =
164
167
  '<div class="alert alert-success">' +
165
168
  '<b>✓ Verbindung erfolgreich</b> (' + data.duration_ms + ' ms)<br>' +
@@ -167,6 +170,7 @@ window.sambaTestConn = async function(btn) {
167
170
  'Basispfad: <code>' + data.base_path + '</code>, Benutzer: <code>' + data.username + '</code><br>' +
168
171
  'Einträge gefunden: <b>' + data.entry_count + '</b>' + (data.truncated ? ' (erste 20 unten)' : '') +
169
172
  (rows ? '<ul style="margin:.5rem 0 0 1rem">' + rows + '</ul>' : '') +
173
+ noteHtml +
170
174
  '</div>';
171
175
  } else {
172
176
  var a = data && data.attempted || {};
@@ -301,11 +305,7 @@ const configuration_workflow = () =>
301
305
  type: "String",
302
306
  required: true,
303
307
  attributes: {
304
- options: [
305
- { value: "if-offered", label: "if-offered (Standard)" },
306
- { value: "required", label: "required (strikt)" },
307
- { value: "disabled", label: "disabled (aus)" },
308
- ],
308
+ options: ["if-offered", "required", "disabled"],
309
309
  },
310
310
  default: "if-offered",
311
311
  }),
@@ -321,11 +321,7 @@ const configuration_workflow = () =>
321
321
  type: "String",
322
322
  required: true,
323
323
  attributes: {
324
- options: [
325
- { value: "if-offered", label: "if-offered (Standard)" },
326
- { value: "required", label: "required (strikt)" },
327
- { value: "disabled", label: "disabled (aus)" },
328
- ],
324
+ options: ["if-offered", "required", "disabled"],
329
325
  },
330
326
  default: "if-offered",
331
327
  }),
@@ -795,10 +791,41 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
795
791
  try {
796
792
  const listing = await withClient(testCfg, async (client) => {
797
793
  // If base_path is set, list it — that also verifies traversal.
794
+ // For the share-root case we first try readdir; some Samba builds
795
+ // reject QUERY_DIRECTORY on the empty root name, so we fall back
796
+ // to stat("") — that already proves the whole handshake
797
+ // (TCP + Negotiate + Session + TREE_CONNECT + Auth) works, which
798
+ // is all the connection test actually promises.
798
799
  const rel = testCfg.base_path ? sanitizeRelativePath(testCfg.base_path) : "";
799
- return await client.readdir(rel);
800
+ try {
801
+ return await client.readdir(rel);
802
+ } catch (err) {
803
+ // Look at both the wrapper error and its underlying cause.
804
+ const causeMsg = String((err && err.cause && err.cause.message) || "");
805
+ const msg = String((err && err.message) || err || "") + " " + causeMsg;
806
+ const isRootProbe = !rel;
807
+ const isRootEnumBug = /0xC0000033|OBJECT_NAME_INVALID|Share-Root/i.test(msg);
808
+ if (isRootProbe && isRootEnumBug) {
809
+ // Fall back: proof-of-life via stat on share root. This
810
+ // confirms TCP + Negotiate + Session + Auth + TREE_CONNECT
811
+ // without hitting the broken QUERY_DIRECTORY path.
812
+ try {
813
+ await client.stat("");
814
+ // Signal to the caller that the connection works, but
815
+ // the share root cannot be enumerated on this server.
816
+ const marker = [];
817
+ marker._rootNotEnumerable = true;
818
+ return marker;
819
+ } catch (statErr) {
820
+ // stat also failed — surface the original error.
821
+ throw err;
822
+ }
823
+ }
824
+ throw err;
825
+ }
800
826
  });
801
827
  const took = Date.now() - started;
828
+ const rootNotEnum = Array.isArray(listing) && listing._rootNotEnumerable === true;
802
829
  return res.json({
803
830
  ok: true,
804
831
  server: testCfg.server,
@@ -814,6 +841,13 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
814
841
  isDirectory: !!(e && (e.isDirectory === true || (e.stats && e.stats.isDirectory && e.stats.isDirectory()))),
815
842
  })),
816
843
  truncated: Array.isArray(listing) && listing.length > 20,
844
+ note: rootNotEnum
845
+ ? "Verbindung + Anmeldung erfolgreich. Der Server erlaubt jedoch " +
846
+ "kein direktes Auflisten des Share-Roots (bekanntes Samba-" +
847
+ "Verhalten mit smb3-client). Bitte setzen Sie einen Basispfad " +
848
+ "in der Plugin-Config (z.\u202fB. einen Unterordner der " +
849
+ "Freigabe) — dann funktioniert der File-Manager vollständig."
850
+ : undefined,
817
851
  });
818
852
  } catch (err) {
819
853
  // Turn opaque SMB / socket errors into actionable hints.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "saltcorn-samba",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
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": {
package/smb-client.js CHANGED
@@ -273,7 +273,39 @@ async function buildClient(config) {
273
273
  */
274
274
  async readdir(rel) {
275
275
  const full = resolvePath(rel);
276
- const dirents = await client.readdir(full, { withFileTypes: true });
276
+ let dirents;
277
+ try {
278
+ dirents = await client.readdir(full, { withFileTypes: true });
279
+ } catch (err) {
280
+ // Some Samba builds (observed on 4.20+/4.23+) reject
281
+ // SMB2 QUERY_DIRECTORY on the *share root* with
282
+ // STATUS_OBJECT_NAME_INVALID (0xC0000033) because smb3-client
283
+ // asks for FileIdBothDirectoryInformation (class 37) on a
284
+ // handle opened with an empty filename. We cannot swap the
285
+ // information class from userland, and Samba refuses both
286
+ // "." (→ STATUS_OBJECT_NAME_NOT_FOUND) and "*" (→ CREATE with
287
+ // wildcard is protocol-illegal). The clean way out is:
288
+ // require the caller to configure a real base_path so every
289
+ // readdir happens inside a directory the server can enumerate.
290
+ const msg = String((err && err.message) || err || "");
291
+ const isRoot = !rel && !basePath;
292
+ const isRootEnumBug =
293
+ /0xC0000033|OBJECT_NAME_INVALID|QUERY_DIRECTORY/i.test(msg);
294
+ if (isRoot && isRootEnumBug) {
295
+ const e = new Error(
296
+ "Das Share-Root lässt sich auf diesem Samba-Server nicht " +
297
+ "direkt auflisten (" + (msg || "QUERY_DIRECTORY failed") + "). " +
298
+ "Bitte in der Plugin-Config einen Basispfad setzen (z.\u202fB. " +
299
+ "einen Unterordner der Freigabe wie „daten“ oder „projekte“) — " +
300
+ "dann funktionieren alle Directory-Listings innerhalb dieses " +
301
+ "Unterordners. Details siehe README, Abschnitt „Troubleshooting → " +
302
+ "QUERY_DIRECTORY auf Share-Root“."
303
+ );
304
+ e.cause = err;
305
+ throw e;
306
+ }
307
+ throw err;
308
+ }
277
309
  // Parallel enrichment. Bounded to a reasonable concurrency to avoid
278
310
  // saturating the SMB session on huge directories.
279
311
  const CHUNK = 16;