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 +79 -0
- package/index.js +45 -11
- package/package.json +1 -1
- package/smb-client.js +33 -1
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
|
-
|
|
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.
|
|
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
|
-
|
|
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;
|