saltcorn-samba 0.3.10 → 0.3.11

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,64 @@ 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.3.11] – 2026-07-05
8
+
9
+ ### Fixed – **Worker-Crash / 502 durch DES-ECB auf Node 17+ / OpenSSL 3**
10
+
11
+ Symptom nach dem 0.3.10-Update:
12
+
13
+ ```
14
+ node:internal/crypto/cipher:117
15
+ Error: error:0308010C:digital envelope routines::unsupported
16
+ at Cipheriv.createCipherBase ...
17
+ at .../@marsaud/smb2/node_modules/ntlm/lib/smbhash.js:46
18
+ code: 'ERR_OSSL_EVP_UNSUPPORTED'
19
+ worker died
20
+ ```
21
+
22
+ Auf der Config-Seite dann: **„Antwort war kein JSON (HTTP 502)“**.
23
+
24
+ **Ursache:** `@marsaud/smb2` benutzt über das transitive Paket `ntlm` den
25
+ Cipher **DES-ECB** zur Berechnung der LM/NTLM-Hashes. Node.js ab Version 17
26
+ ist gegen OpenSSL 3 gebaut, das DES-ECB standardmäßig blockiert. Der
27
+ `createCipheriv("des-ecb", ...)`-Aufruf wirft dann synchron
28
+ `ERR_OSSL_EVP_UNSUPPORTED`. Weil der Fehler synchron aus tiefen Callback-
29
+ Aufrufen kommt, tötet er den Saltcorn-Worker-Prozess → der Reverse-Proxy
30
+ liefert 502 → die Route liefert kein JSON.
31
+
32
+ **Fixes in diesem Release:**
33
+
34
+ 1. **Präventiver Check in `smb-client.js`:** Beim ersten Aufbau eines
35
+ SMB-Clients wird geprüft, ob `crypto.createCipheriv("des-ecb", ...)`
36
+ funktioniert. Wenn nicht, wird eine saubere, ausführliche deutsche
37
+ Fehlermeldung mit Lieferanleitung geworfen (`E_LEGACY_CRYPTO`) — der
38
+ fehlerhafte NTLM-Code wird gar nicht erst betreten, der Worker überlebt.
39
+ 2. **`/sambatest`-Route:** Der Check läuft zusätzlich explizit **vor**
40
+ dem `withClient`-Aufruf und liefert JSON mit `code: "E_LEGACY_CRYPTO"`,
41
+ `error`, `hint`, `node_version` und `openssl_version` — damit steht der
42
+ Diagnose-Grund direkt in der UI, nicht im Server-Log.
43
+ 3. **Fehler-Mapping erweitert:** Falls der Crash doch mal aus einem
44
+ anderen Pfad kommt (z. B. spätere Reconnect-Versuche), erkennt die
45
+ Catch-Logik jetzt auch `ERR_OSSL_EVP_UNSUPPORTED`,
46
+ `digital envelope routines` und `unsupported` und liefert einen
47
+ verständlichen Hinweis.
48
+ 4. **README:** Neuer Abschnitt „Troubleshooting“ mit Setup-Rezepten für
49
+ `NODE_OPTIONS=--openssl-legacy-provider` (Umgebungsvariable, systemd,
50
+ Docker/Compose, PM2, direkter Aufruf) plus Tabelle mit den häufigsten
51
+ Verbindungsfehlern und Diagnose-Kommandos.
52
+
53
+ ### Notes
54
+ - Das Plugin ändert keinen Node-Startparameter selbst — das können wir aus
55
+ Sicherheits- und Prozessgründen nicht (Node-Flags müssen beim Prozess-
56
+ Start gesetzt werden). Der Fix ist eine saubere Fehlererkennung mit
57
+ Lösungsanleitung.
58
+ - Sobald Saltcorn mit `NODE_OPTIONS=--openssl-legacy-provider` läuft,
59
+ funktionieren Verbindungstest und alle SMB-Operationen ohne weitere
60
+ Anpassung.
61
+ - **Roadmap:** Migration weg von `@marsaud/smb2` (letzter Release 2020) hin
62
+ zu einer aktiv gepflegten SMB-Client-Bibliothek, damit das
63
+ Legacy-Provider-Flag nicht mehr nötig ist.
64
+
7
65
  ## [0.3.10] – 2026-07-05
8
66
 
9
67
  ### Fixed – **DNS-Fehler: `getaddrinfo ENOTFOUND "host:445"`**
package/README.md CHANGED
@@ -331,6 +331,95 @@ Ab dann taucht das Plugin im Plugins-Store jeder Saltcorn-Instanz auf.
331
331
 
332
332
  ---
333
333
 
334
+ ## Troubleshooting
335
+
336
+ ### `ERR_OSSL_EVP_UNSUPPORTED` / `digital envelope routines::unsupported` / Worker stirbt / 502 Bad Gateway
337
+
338
+ Die Bibliothek `@marsaud/smb2` benutzt intern (im transitiven Paket `ntlm`)
339
+ den Cipher **DES-ECB**, um die NTLM/LM-Hashes zu berechnen. Ab **Node.js 17**
340
+ mit **OpenSSL 3** ist DES-ECB standardmäßig deaktiviert. Der Aufruf wirft
341
+ dann synchron:
342
+
343
+ ```
344
+ Error: error:0308010C:digital envelope routines::unsupported
345
+ code: 'ERR_OSSL_EVP_UNSUPPORTED'
346
+ ```
347
+
348
+ Weil der Fehler synchron aus dem NTLM-Modul kommt, **tötet er den
349
+ Saltcorn-Worker-Prozess**. Der davor sitzende Reverse-Proxy meldet dann
350
+ `502 Bad Gateway` und die Antwort ist kein JSON — was auf der Config-Seite
351
+ als "Antwort war kein JSON (HTTP 502)" erscheint.
352
+
353
+ **Lösung:** Saltcorn mit dem Node-Flag `--openssl-legacy-provider` starten.
354
+ Je nach Setup:
355
+
356
+ **(a) Umgebungsvariable (empfohlen, wirkt auf jeden Node-Aufruf)**
357
+
358
+ ```bash
359
+ export NODE_OPTIONS="--openssl-legacy-provider"
360
+ saltcorn serve
361
+ ```
362
+
363
+ **(b) systemd-Unit** (`/etc/systemd/system/saltcorn.service`)
364
+
365
+ ```ini
366
+ [Service]
367
+ Environment=NODE_OPTIONS=--openssl-legacy-provider
368
+ ExecStart=/usr/bin/saltcorn serve
369
+ ```
370
+
371
+ Danach:
372
+ ```bash
373
+ sudo systemctl daemon-reload
374
+ sudo systemctl restart saltcorn
375
+ ```
376
+
377
+ **(c) Docker / docker-compose**
378
+
379
+ ```yaml
380
+ services:
381
+ saltcorn:
382
+ image: saltcorn/saltcorn:latest
383
+ environment:
384
+ NODE_OPTIONS: "--openssl-legacy-provider"
385
+ ```
386
+
387
+ **(d) PM2**
388
+
389
+ ```bash
390
+ pm2 delete saltcorn
391
+ NODE_OPTIONS="--openssl-legacy-provider" pm2 start saltcorn -- serve
392
+ pm2 save
393
+ ```
394
+
395
+ **(e) Direkter Aufruf**
396
+
397
+ ```bash
398
+ node --openssl-legacy-provider $(which saltcorn) serve
399
+ ```
400
+
401
+ Nach dem Neustart im Config-Dialog erneut auf „Verbindung jetzt testen“
402
+ klicken — die Fehlermeldung sollte weg sein.
403
+
404
+ > **Hinweis zur Sicherheit:** `--openssl-legacy-provider` reaktiviert die
405
+ > alten Cipher für den gesamten Node-Prozess. Das ist für NTLM-Auth gegen
406
+ > Samba akzeptabel (der Cipher wird nur zur Hash-Berechnung genutzt, nicht
407
+ > für den Transport), aber ein Grund, mittelfristig auf eine gepflegte
408
+ > SMB-Client-Bibliothek zu wechseln — siehe Roadmap.
409
+
410
+ ### Weitere häufige Fehler
411
+
412
+ | Symptom | Ursache | Lösung |
413
+ |---|---|---|
414
+ | `getaddrinfo ENOTFOUND host:445` | Bis v0.3.9 wurde der Port in den UNC-Pfad geschrieben | Update auf **v0.3.10+** |
415
+ | `ECONNREFUSED :445` | SMB-Dienst läuft nicht oder Firewall blockt Port 445 | `nc -vz <server> 445` vom Saltcorn-Host testen |
416
+ | `ETIMEDOUT` | Kein Netzwerkpfad (VLAN/Docker-Bridge/VPN) | Vom Saltcorn-Container aus `ping` + `nc -vz` prüfen |
417
+ | `STATUS_LOGON_FAILURE` | User/Passwort/Domain falsch | Zugangsdaten prüfen. Moderne Samba-Server erlauben keine leeren Passwörter |
418
+ | `STATUS_BAD_NETWORK_NAME` | Share existiert nicht oder Schreibweise falsch | `smbclient -L //server -U user` zur Kontrolle |
419
+ | Nur SMBv1 verfügbar | Server bietet SMB2/3 nicht an | In `smb.conf`: `min protocol = SMB2` |
420
+
421
+ ---
422
+
334
423
  ## Bekannte Grenzen
335
424
 
336
425
  - Dateien werden komplett gepuffert (`readFile` / `writeFile`). Für
package/index.js CHANGED
@@ -47,6 +47,8 @@ const {
47
47
  mimeFromName,
48
48
  sanitizeRelativePath,
49
49
  sanitizeFilename,
50
+ checkLegacyCryptoAvailable,
51
+ legacyCryptoErrorMessage,
50
52
  } = require("./smb-client");
51
53
  const treeView = require("./tree-view");
52
54
  const fileManagerView = require("./filemanager-view");
@@ -747,6 +749,33 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
747
749
  if (!testCfg.server) return jsonError(res, 400, "Please enter a Server (hostname or IP).");
748
750
  if (!testCfg.share) return jsonError(res, 400, "Please enter a Share name.");
749
751
 
752
+ // Vor jedem echten SMB-Aufruf: Node/OpenSSL-Legacy-Cipher-Check.
753
+ // Falls DES-ECB nicht verfügbar ist (Node 17+ / OpenSSL 3 ohne
754
+ // --openssl-legacy-provider), übergeben wir gar nicht erst an ntlm,
755
+ // sonst crasht der Worker synchron und der Proxy meldet 502.
756
+ const cc = checkLegacyCryptoAvailable();
757
+ if (!cc.ok) {
758
+ return res.status(200).json({
759
+ ok: false,
760
+ error: legacyCryptoErrorMessage(cc.message),
761
+ code: "E_LEGACY_CRYPTO",
762
+ hint:
763
+ "Saltcorn mit --openssl-legacy-provider starten. " +
764
+ "Am einfachsten: die Umgebungsvariable NODE_OPTIONS=--openssl-legacy-provider setzen und Saltcorn neu starten. " +
765
+ "Details und Beispiele für systemd / Docker / PM2 stehen in der README des Plugins.",
766
+ attempted: {
767
+ server: testCfg.server,
768
+ share: testCfg.share,
769
+ port: testCfg.port,
770
+ domain: testCfg.domain,
771
+ username: testCfg.username || "(anonymous)",
772
+ base_path: testCfg.base_path || "(share root)",
773
+ },
774
+ node_version: process.version,
775
+ openssl_version: (process.versions && process.versions.openssl) || null,
776
+ });
777
+ }
778
+
750
779
  const started = Date.now();
751
780
  try {
752
781
  const listing = await withClient(testCfg, async (client) => {
@@ -798,6 +827,8 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
798
827
  hint = "The server may be offering only SMBv1 which this plugin does not support. Enable SMB2 / SMB3 on the Samba server (min protocol = SMB2 in smb.conf).";
799
828
  else if (m.includes("traversal") || m.includes("path"))
800
829
  hint = "The Base path could not be validated. It must be a relative sub-directory (e.g. 'projects/2026'), never start with / or \\, and must not contain '..'.";
830
+ else if (code === "ERR_OSSL_EVP_UNSUPPORTED" || code === "E_LEGACY_CRYPTO" || m.includes("digital envelope routines") || m.includes("unsupported"))
831
+ hint = "Node blockiert die Legacy-Cipher (DES-ECB), die @marsaud/smb2 für NTLM benötigt. Saltcorn mit NODE_OPTIONS=--openssl-legacy-provider neu starten. Details in der README.";
801
832
 
802
833
  return res.status(200).json({
803
834
  ok: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "saltcorn-samba",
3
- "version": "0.3.10",
3
+ "version": "0.3.11",
4
4
  "description": "Saltcorn plugin: browse, upload, rename and delete files on a Samba/CIFS share. 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
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  const path = require("path");
10
+ const crypto = require("crypto");
10
11
 
11
12
  // @marsaud/smb2 is loaded lazily so the pure sanitizer helpers exported by
12
13
  // this module can be required (e.g. from unit tests) without needing the
@@ -18,6 +19,57 @@ function getSMB2() {
18
19
  return _SMB2;
19
20
  }
20
21
 
22
+ // ---------------------------------------------------------------------------
23
+ // OpenSSL 3 / Node 17+ compatibility check
24
+ // ---------------------------------------------------------------------------
25
+ //
26
+ // @marsaud/smb2 -> ntlm -> smbhash.js benutzt DES-ECB für die LM/NTLM-Hashes.
27
+ // Node 17+ mit OpenSSL 3 blockiert DES-ECB standardmäßig; der Aufruf wirft
28
+ // dann `error:0308010C:digital envelope routines::unsupported`
29
+ // (ERR_OSSL_EVP_UNSUPPORTED). Der Fehler fliegt synchron und tötet den
30
+ // Saltcorn-Worker → der Reverse-Proxy meldet 502 Bad Gateway.
31
+ //
32
+ // Hier fangen wir das VOR dem eigentlichen SMB-Aufruf ab und liefern eine
33
+ // verständliche Fehlermeldung mit Lösungsanleitung.
34
+ let _cryptoOK = null;
35
+ function checkLegacyCryptoAvailable() {
36
+ if (_cryptoOK !== null) return _cryptoOK;
37
+ try {
38
+ // DES-ECB genau so aufrufen, wie es ntlm intern tut.
39
+ // Wenn OpenSSL 3 ohne Legacy-Provider läuft, wirft das hier.
40
+ const key = Buffer.alloc(8, 0);
41
+ const c = crypto.createCipheriv("des-ecb", key, null);
42
+ c.setAutoPadding(false);
43
+ c.update(Buffer.alloc(8, 0));
44
+ c.final();
45
+ _cryptoOK = { ok: true };
46
+ } catch (e) {
47
+ _cryptoOK = {
48
+ ok: false,
49
+ code: e && e.code,
50
+ message: (e && e.message) || String(e),
51
+ };
52
+ }
53
+ return _cryptoOK;
54
+ }
55
+
56
+ /** Baut eine deutsche Erklärung für den Legacy-Provider-Fehler. */
57
+ function legacyCryptoErrorMessage(detail) {
58
+ return (
59
+ "NTLM-Authentifizierung nicht möglich: Node.js blockiert die von " +
60
+ "@marsaud/smb2 benötigten Legacy-Cipher (DES-ECB). Das ist ab Node 17 " +
61
+ "mit OpenSSL 3 der Standard. " +
62
+ "Lösung: Saltcorn mit dem Flag --openssl-legacy-provider starten. " +
63
+ "Beispiele: " +
64
+ "(1) direkt: `node --openssl-legacy-provider node_modules/.bin/saltcorn serve` " +
65
+ "(2) per Umgebungsvariable: `NODE_OPTIONS=--openssl-legacy-provider saltcorn serve` " +
66
+ "(3) systemd-Unit: `Environment=NODE_OPTIONS=--openssl-legacy-provider` " +
67
+ "(4) Docker: im Compose-File `environment: NODE_OPTIONS: --openssl-legacy-provider`. " +
68
+ "Danach Saltcorn neu starten. Details siehe README des Plugins. " +
69
+ (detail ? "[Original: " + detail + "]" : "")
70
+ );
71
+ }
72
+
21
73
  // ---------------------------------------------------------------------------
22
74
  // Path helpers
23
75
  // ---------------------------------------------------------------------------
@@ -116,6 +168,15 @@ function buildClient(config) {
116
168
  if (!share) throw new Error("Samba: share missing");
117
169
  if (/[\\/]/.test(share)) throw new Error("Samba: share must not contain slashes");
118
170
 
171
+ // WICHTIG: OpenSSL-Legacy-Check VOR jedem SMB2-Aufruf. Sonst crasht
172
+ // ntlm/smbhash.js synchron und tötet den Worker (→ 502 am Proxy).
173
+ const cc = checkLegacyCryptoAvailable();
174
+ if (!cc.ok) {
175
+ const err = new Error(legacyCryptoErrorMessage(cc.message));
176
+ err.code = "E_LEGACY_CRYPTO";
177
+ throw err;
178
+ }
179
+
119
180
  // Host und Port sauber trennen. Der Server-String darf NICHT im share-UNC-
120
181
  // Pfad landen, sonst versucht Node's DNS "host:port" als Hostnamen aufzulösen
121
182
  // (getaddrinfo ENOTFOUND "1.2.3.4:445"). @marsaud/smb2 nimmt den Port über
@@ -314,4 +375,6 @@ module.exports = {
314
375
  toSmbPath,
315
376
  toSmbUrl,
316
377
  mimeFromName,
378
+ checkLegacyCryptoAvailable,
379
+ legacyCryptoErrorMessage,
317
380
  };