saltcorn-samba 0.3.4 → 0.3.6
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 +48 -0
- package/README.md +43 -11
- package/index.js +333 -25
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,54 @@ 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.6] – 2026-07-05
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- **`JSON.parse: unexpected character at line 1 column 1` beim Test-Button behoben.**
|
|
11
|
+
Der Aufruf von `POST /sambatest` lief zuvor gegen die globale
|
|
12
|
+
CSRF-Middleware von Saltcorn, die bei ungültigem/fehlendem Token eine
|
|
13
|
+
HTML-Seite zurückliefert – der Browser konnte sie nicht als JSON parsen.
|
|
14
|
+
Fix in drei Schichten:
|
|
15
|
+
1. Route ist jetzt mit `noCsrf: true` markiert (Sicherheit weiterhin durch
|
|
16
|
+
die harte Admin-Prüfung `roleOf(req) !== 1` im Handler garantiert;
|
|
17
|
+
siehe [saltcorn plugin_routes_handler.js](https://github.com/saltcorn/saltcorn/blob/master/packages/server/plugin_routes_handler.js)).
|
|
18
|
+
2. Der Client sendet nun als `application/x-www-form-urlencoded`
|
|
19
|
+
(statt JSON) und legt das `_csrf`-Token direkt in den Body – so
|
|
20
|
+
würde auch ein aktiver CSRF-Check passieren.
|
|
21
|
+
3. Das Token wird jetzt zuverlässig aus drei Quellen gesucht:
|
|
22
|
+
dem versteckten `<input name="_csrf">` des Formulars, dem
|
|
23
|
+
`<meta name="csrf-token">`-Tag und `window._sc_globalCsrf`.
|
|
24
|
+
- **Klartext-Fehler statt kryptischem Parser-Crash.**
|
|
25
|
+
Wenn der Server doch mal HTML statt JSON liefert (z. B. Login-Redirect,
|
|
26
|
+
Plugin nicht neu geladen), zeigt der Test-Button jetzt eine deutliche
|
|
27
|
+
Meldung mit HTTP-Status und einer aufklappbaren Rohantwort statt eines
|
|
28
|
+
hilflosen „unexpected character“.
|
|
29
|
+
|
|
30
|
+
## [0.3.5] – 2026-07-05
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
- **Testverbindung im Konfigurations-Wizard.**
|
|
34
|
+
Auf Schritt 1 „Samba-Server“ gibt es jetzt einen Button
|
|
35
|
+
„→ Verbindung jetzt testen“. Er öffnet eine SMB-Sitzung mit den
|
|
36
|
+
aktuell im Formular stehenden Werten (ohne zu speichern), meldet den
|
|
37
|
+
Benutzer an und listet die Wurzel bzw. den Basispfad des Shares auf.
|
|
38
|
+
Bei Erfolg werden Dauer, Anzahl Einträge und die ersten 20 Namen
|
|
39
|
+
angezeigt; bei Fehlschlag der genaue SMB-Fehler plus konkreter
|
|
40
|
+
Handlungshinweis auf Deutsch (z. B. DNS, ECONNREFUSED, LOGON_FAILURE,
|
|
41
|
+
BAD_NETWORK_NAME, SMBv1).
|
|
42
|
+
- **Neue Route `POST /sambatest` (nur Admin).** Nimmt die Serverdaten
|
|
43
|
+
als JSON entgegen, gibt strukturiertes Ergebnis mit `ok`, `error`,
|
|
44
|
+
`code`, `hint`, `attempted` zurück. Verändert keine Konfiguration.
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
- **Alle Feld-Beschreibungen in Schritt 1 komplett neu, ausführlich
|
|
48
|
+
auf Deutsch.** Jedes Feld (Server, Freigabe, Domäne, Benutzer,
|
|
49
|
+
Passwort, Basispfad, Port) erklärt Format, Docker-Fallstricke,
|
|
50
|
+
Standardwerte und typische Fehlerquellen. Schritt 2 ebenfalls
|
|
51
|
+
vollständig lokalisiert.
|
|
52
|
+
- Schritt-Namen: „Samba server“ → „Samba-Server“, „Access & permissions“
|
|
53
|
+
→ „Zugriff & Berechtigungen“.
|
|
54
|
+
|
|
7
55
|
## [0.3.4] – 2026-07-05
|
|
8
56
|
|
|
9
57
|
### Fixed
|
package/README.md
CHANGED
|
@@ -98,20 +98,22 @@ docker exec -it <saltcorn-container> \
|
|
|
98
98
|
Nach der Installation unter *Einstellungen → Plugins → saltcorn-samba →
|
|
99
99
|
Configure* ausfüllen. Die Konfiguration ist zweistufig:
|
|
100
100
|
|
|
101
|
-
### Schritt 1 — *Samba
|
|
101
|
+
### Schritt 1 — *Samba-Server*
|
|
102
102
|
|
|
103
103
|
| Feld | Beispiel | Beschreibung |
|
|
104
104
|
|---|---|---|
|
|
105
|
-
| Server | `192.168.1.
|
|
106
|
-
| Share
|
|
107
|
-
|
|
|
108
|
-
|
|
|
109
|
-
|
|
|
110
|
-
|
|
|
111
|
-
| Port | `445` | Standard
|
|
112
|
-
| SMB host visible to clients | `fileserver.lan` | *optional* — Host, der in `smb://`-Links auftaucht (nützlich in Docker) |
|
|
105
|
+
| **Server** | `192.168.1.20`, `nas01`, `fileserver.local` | Hostname oder IP des Samba-Servers, **ohne** `smb://`-Präfix und **ohne** Backslashes. Muss aus Sicht des Saltcorn-Prozesses auflösbar sein. **In Docker:** nicht `localhost` verwenden – stattdessen LAN-IP des Hosts oder Container ins passende Netzwerk hängen. |
|
|
106
|
+
| **Freigabe / Share-Name** | `daten`, `public`, `projekte` | Name der SMB-Freigabe **ohne** Slashes. Auf dem Server als `[NAME]`-Abschnitt in `smb.conf` bzw. unter Windows als Freigabename sichtbar. Nicht die Ordner-Bezeichnung. |
|
|
107
|
+
| **Domäne / Arbeitsgruppe** | `WORKGROUP`, `CONTOSO` | Meist `WORKGROUP` (Standard). Für Active Directory: NetBIOS-Name der Domäne, nicht der FQDN. |
|
|
108
|
+
| **Benutzername** | `saltcorn` | Samba-/AD-Benutzer, **nicht** im Format `DOMAIN\user` (Domäne gehört in das eigene Feld). Leer lassen für anonymen Zugriff (nur bei `guest ok = yes`). |
|
|
109
|
+
| **Passwort** | *(secret)* | Samba nutzt ein eigenes Passwort (`smbpasswd`), nicht zwingend das Linux-Login. Moderne Server lehnen leere Passwörter ab. |
|
|
110
|
+
| **Basispfad** | `projekte/2026` | Optional. Relativ, mit Slashes, **ohne** führenden `/`. Beschränkt jeden Zugriff auf dieses Unterverzeichnis der Freigabe. `..` und absolute Pfade werden abgelehnt. |
|
|
111
|
+
| **TCP-Port** | `445` | Standard SMB2/3 über TCP. **SMBv1 (139) wird nicht unterstützt** – auf dem Server `min protocol = SMB2` setzen. |
|
|
113
112
|
|
|
114
|
-
|
|
113
|
+
> **Tipp:** Bevor Sie speichern, klicken Sie auf **„→ Verbindung jetzt
|
|
114
|
+
> testen“** – siehe Abschnitt [Verbindung testen](#verbindung-testen).
|
|
115
|
+
|
|
116
|
+
### Schritt 2 — *Zugriff & Berechtigungen*
|
|
115
117
|
|
|
116
118
|
| Feld | Default | Beschreibung |
|
|
117
119
|
|---|---|---|
|
|
@@ -129,7 +131,37 @@ Configure* ausfüllen. Die Konfiguration ist zweistufig:
|
|
|
129
131
|
- Nur die Features aktivieren, die wirklich gebraucht werden.
|
|
130
132
|
- Auf dem Samba-Server einen separaten Nutzer mit passenden Rechten anlegen
|
|
131
133
|
(read-only wenn nur gelesen werden soll).
|
|
132
|
-
- Zusätzlich mit *
|
|
134
|
+
- Zusätzlich mit *Basispfad* den Zugriff auf ein Unterverzeichnis begrenzen.
|
|
135
|
+
|
|
136
|
+
### Verbindung testen
|
|
137
|
+
|
|
138
|
+
Direkt im Konfigurations-Wizard (Schritt 1) gibt es den Button
|
|
139
|
+
**„→ Verbindung jetzt testen“**. Er sendet die aktuell im Formular
|
|
140
|
+
stehenden Werte an die interne Route `POST /sambatest` (nur für Admins),
|
|
141
|
+
baut eine SMB-Verbindung auf und listet den Basispfad bzw. die
|
|
142
|
+
Share-Wurzel auf. Es werden dabei **keine** Daten gespeichert oder
|
|
143
|
+
geschrieben.
|
|
144
|
+
|
|
145
|
+
- **Erfolg (grün):** Dauer der Verbindung, Anzahl gefundener Einträge
|
|
146
|
+
und die ersten 20 Namen (Datei/Ordner) werden angezeigt.
|
|
147
|
+
- **Fehler (rot):** Der SMB-/Netzwerk-Fehler wird im Klartext gezeigt,
|
|
148
|
+
zusammen mit einem konkreten Handlungshinweis auf Deutsch, z. B.:
|
|
149
|
+
- `ECONNREFUSED` → Samba läuft nicht oder Firewall/Docker blockt 445
|
|
150
|
+
- `ETIMEDOUT` → Host nicht erreichbar (Ping/`nc -vz`)
|
|
151
|
+
- `ENOTFOUND` → DNS-Auflösung fehlgeschlagen (IP verwenden)
|
|
152
|
+
- `LOGON_FAILURE` / `ACCESS_DENIED` → Benutzer, Passwort oder Domäne falsch
|
|
153
|
+
- `BAD_NETWORK_NAME` → Share-Name existiert so nicht auf dem Server
|
|
154
|
+
- `SMB1 / protocol` → Server bietet nur SMBv1 an (nicht unterstützt)
|
|
155
|
+
|
|
156
|
+
Die Route kann zusätzlich auch von Skripten aufgerufen werden:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
curl -X POST -H 'Content-Type: application/json' \
|
|
160
|
+
-H "X-CSRF-Token: $CSRF" \
|
|
161
|
+
--cookie "$COOKIES" \
|
|
162
|
+
-d '{"server":"192.168.1.20","share":"daten","username":"u","password":"p"}' \
|
|
163
|
+
https://saltcorn.example.com/sambatest
|
|
164
|
+
```
|
|
133
165
|
|
|
134
166
|
---
|
|
135
167
|
|
package/index.js
CHANGED
|
@@ -54,103 +54,299 @@ const DEFAULT_DENIED_EXT = [
|
|
|
54
54
|
// Plugin configuration
|
|
55
55
|
// ---------------------------------------------------------------------------
|
|
56
56
|
|
|
57
|
+
// HTML block injected as first "field" of step 1 — Saltcorn renders fields
|
|
58
|
+
// with type="String" and input_type="custom_html" as raw HTML. This gives
|
|
59
|
+
// admins a Test-Verbindung button next to the form itself.
|
|
60
|
+
const CONNECTION_TEST_HTML = `
|
|
61
|
+
<div class="card mb-3" style="border-left:4px solid #0d6efd">
|
|
62
|
+
<div class="card-body">
|
|
63
|
+
<h5 class="card-title" style="margin-top:0">Verbindung testen</h5>
|
|
64
|
+
<p class="card-text" style="margin-bottom:.6rem">
|
|
65
|
+
Prüfen Sie die eingegebenen Zugangsdaten <b>bevor</b> Sie speichern.
|
|
66
|
+
Der Test öffnet eine SMB-Verbindung zum Server, meldet den angegebenen
|
|
67
|
+
Benutzer an und listet das Wurzel-Verzeichnis des Shares (bzw. den
|
|
68
|
+
Basispfad, falls angegeben) auf. Es werden keine Daten geschrieben.
|
|
69
|
+
</p>
|
|
70
|
+
<button type="button" class="btn btn-primary" onclick="sambaTestConn(this)">
|
|
71
|
+
→ Verbindung jetzt testen
|
|
72
|
+
</button>
|
|
73
|
+
<div id="sambaTestOut" style="margin-top:.8rem"></div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
<script>
|
|
77
|
+
window.sambaTestConn = async function(btn) {
|
|
78
|
+
var out = document.getElementById('sambaTestOut');
|
|
79
|
+
var form = btn.closest('form');
|
|
80
|
+
if (!form) { out.innerHTML = '<div class="alert alert-danger">Formular nicht gefunden.</div>'; return; }
|
|
81
|
+
function v(n){ var el = form.querySelector('[name="'+n+'"]'); return el ? el.value : ''; }
|
|
82
|
+
var payload = {
|
|
83
|
+
server: v('server'),
|
|
84
|
+
share: v('share'),
|
|
85
|
+
domain: v('domain'),
|
|
86
|
+
username: v('username'),
|
|
87
|
+
password: v('password'),
|
|
88
|
+
base_path: v('base_path'),
|
|
89
|
+
port: v('port')
|
|
90
|
+
};
|
|
91
|
+
if (!payload.server || !payload.share) {
|
|
92
|
+
out.innerHTML = '<div class="alert alert-warning">Bitte mindestens <b>Server</b> und <b>Share</b> ausfüllen.</div>';
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// Robust CSRF-Token-Suche: erst das versteckte Feld des Formulars,
|
|
96
|
+
// dann Meta-Tag, dann globales window._sc_globalCsrf.
|
|
97
|
+
var csrfEl = form.querySelector('input[name="_csrf"]');
|
|
98
|
+
var csrf =
|
|
99
|
+
(csrfEl && csrfEl.value) ||
|
|
100
|
+
((document.querySelector('meta[name="csrf-token"]') || {}).content) ||
|
|
101
|
+
(window._sc_globalCsrf) ||
|
|
102
|
+
'';
|
|
103
|
+
btn.disabled = true;
|
|
104
|
+
var oldTxt = btn.textContent;
|
|
105
|
+
btn.textContent = 'Teste …';
|
|
106
|
+
out.innerHTML = '<div class="text-muted">Verbindung wird aufgebaut … (bis zu 30 s)</div>';
|
|
107
|
+
try {
|
|
108
|
+
// Als URL-encoded senden – so findet Saltcorns CSRF-Middleware das
|
|
109
|
+
// Token direkt im Body (req.body._csrf), unabhängig von der
|
|
110
|
+
// Header-Konfiguration. Zusätzlich das Token als Header schicken.
|
|
111
|
+
var params = new URLSearchParams();
|
|
112
|
+
params.set('_csrf', csrf);
|
|
113
|
+
Object.keys(payload).forEach(function(k){ params.set(k, payload[k] || ''); });
|
|
114
|
+
var r = await fetch('/sambatest', {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
credentials: 'same-origin',
|
|
117
|
+
headers: {
|
|
118
|
+
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
119
|
+
'Accept': 'application/json',
|
|
120
|
+
'X-CSRF-Token': csrf,
|
|
121
|
+
'CSRF-Token': csrf,
|
|
122
|
+
'X-Requested-With': 'XMLHttpRequest'
|
|
123
|
+
},
|
|
124
|
+
body: params.toString()
|
|
125
|
+
});
|
|
126
|
+
// Defensiv: kommt HTML statt JSON zurück (z. B. Login-Redirect), Text anzeigen.
|
|
127
|
+
var raw = await r.text();
|
|
128
|
+
var data;
|
|
129
|
+
try { data = JSON.parse(raw); }
|
|
130
|
+
catch (parseErr) {
|
|
131
|
+
var short = raw.replace(/<[^>]+>/g,'').replace(/\s+/g,' ').trim().slice(0, 300);
|
|
132
|
+
out.innerHTML =
|
|
133
|
+
'<div class="alert alert-danger">' +
|
|
134
|
+
'<b>✗ Antwort war kein JSON</b> (HTTP ' + r.status + ')<br>' +
|
|
135
|
+
'Mögliche Ursachen: nicht als Admin angemeldet (Login-Redirect), CSRF-Token ungültig, ' +
|
|
136
|
+
'oder die Route <code>/sambatest</code> ist noch nicht registriert ' +
|
|
137
|
+
'(Plugin neu installieren bzw. Saltcorn neu starten).<br>' +
|
|
138
|
+
'<details style="margin-top:.4rem"><summary>Antwort des Servers</summary>' +
|
|
139
|
+
'<pre style="white-space:pre-wrap;margin-top:.4rem">' +
|
|
140
|
+
short.replace(/[<>&]/g, function(c){return {'<':'<','>':'>','&':'&'}[c];}) +
|
|
141
|
+
'</pre></details>' +
|
|
142
|
+
'</div>';
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (data && data.ok) {
|
|
146
|
+
var rows = (data.entries||[]).map(function(e){
|
|
147
|
+
return '<li>'+ (e.isDirectory?'📁 ':'📄 ') + String(e.name).replace(/[<>&]/g,'?') +'</li>';
|
|
148
|
+
}).join('');
|
|
149
|
+
out.innerHTML =
|
|
150
|
+
'<div class="alert alert-success">' +
|
|
151
|
+
'<b>✓ Verbindung erfolgreich</b> (' + data.duration_ms + ' ms)<br>' +
|
|
152
|
+
'Server: <code>' + data.server + ':' + data.port + '</code>, Share: <code>' + data.share + '</code>, ' +
|
|
153
|
+
'Basispfad: <code>' + data.base_path + '</code>, Benutzer: <code>' + data.username + '</code><br>' +
|
|
154
|
+
'Einträge gefunden: <b>' + data.entry_count + '</b>' + (data.truncated ? ' (erste 20 unten)' : '') +
|
|
155
|
+
(rows ? '<ul style="margin:.5rem 0 0 1rem">' + rows + '</ul>' : '') +
|
|
156
|
+
'</div>';
|
|
157
|
+
} else {
|
|
158
|
+
var a = data && data.attempted || {};
|
|
159
|
+
out.innerHTML =
|
|
160
|
+
'<div class="alert alert-danger">' +
|
|
161
|
+
'<b>✗ Verbindung fehlgeschlagen</b><br>' +
|
|
162
|
+
'Fehler: <code>' + String(data && data.error || 'Unbekannt').replace(/[<>&]/g,'?') + '</code>' +
|
|
163
|
+
(data && data.code ? ' <span class="text-muted">(' + data.code + ')</span>' : '') + '<br>' +
|
|
164
|
+
(data && data.hint ? '<div style="margin-top:.4rem"><b>Hinweis:</b> ' + String(data.hint).replace(/[<>&]/g,'?') + '</div>' : '') +
|
|
165
|
+
'<details style="margin-top:.4rem"><summary>Versuchte Verbindungsdaten</summary>' +
|
|
166
|
+
'<table class="table table-sm" style="margin-top:.4rem">' +
|
|
167
|
+
'<tr><td>Server</td><td><code>' + (a.server||'') + ':' + (a.port||'') + '</code></td></tr>' +
|
|
168
|
+
'<tr><td>Share</td><td><code>' + (a.share||'') + '</code></td></tr>' +
|
|
169
|
+
'<tr><td>Basispfad</td><td><code>' + (a.base_path||'') + '</code></td></tr>' +
|
|
170
|
+
'<tr><td>Domäne</td><td><code>' + (a.domain||'') + '</code></td></tr>' +
|
|
171
|
+
'<tr><td>Benutzer</td><td><code>' + (a.username||'') + '</code></td></tr>' +
|
|
172
|
+
'</table></details>' +
|
|
173
|
+
'</div>';
|
|
174
|
+
}
|
|
175
|
+
} catch (e) {
|
|
176
|
+
out.innerHTML = '<div class="alert alert-danger">Fehler beim Aufruf von /sambatest: ' +
|
|
177
|
+
String(e && e.message || e).replace(/[<>&]/g,'?') + '</div>';
|
|
178
|
+
} finally {
|
|
179
|
+
btn.disabled = false;
|
|
180
|
+
btn.textContent = oldTxt;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
</script>
|
|
184
|
+
`;
|
|
185
|
+
|
|
57
186
|
const configuration_workflow = () =>
|
|
58
187
|
new Workflow({
|
|
59
188
|
steps: [
|
|
60
189
|
{
|
|
61
|
-
name: "Samba
|
|
190
|
+
name: "Samba-Server",
|
|
62
191
|
form: () =>
|
|
63
192
|
new Form({
|
|
64
193
|
fields: [
|
|
65
194
|
new Field({
|
|
66
195
|
name: "server",
|
|
67
|
-
label: "Server",
|
|
68
|
-
sublabel:
|
|
196
|
+
label: "Server (Hostname oder IP)",
|
|
197
|
+
sublabel:
|
|
198
|
+
"Adresse des Samba-Servers <b>ohne</b> smb://-Präfix und ohne Backslashes. " +
|
|
199
|
+
"Beispiele: <code>192.168.1.20</code>, <code>fileserver.local</code>, <code>nas01</code>. " +
|
|
200
|
+
"Läuft Saltcorn in Docker, ist <code>localhost</code> → der Container selbst; nutzen Sie die LAN-IP " +
|
|
201
|
+
"des Host-Systems (nicht 127.0.0.1) oder verbinden Sie den Container ins gleiche Bridge/Host-Netzwerk. " +
|
|
202
|
+
"Der Hostname muss <b>aus Sicht des Saltcorn-Prozesses</b> auflösbar sein.",
|
|
69
203
|
type: "String",
|
|
70
204
|
required: true,
|
|
71
205
|
}),
|
|
72
206
|
new Field({
|
|
73
207
|
name: "share",
|
|
74
|
-
label: "Share
|
|
75
|
-
sublabel:
|
|
208
|
+
label: "Freigabe / Share-Name",
|
|
209
|
+
sublabel:
|
|
210
|
+
"Name der SMB-Freigabe <b>ohne</b> Schrägstriche. Auf dem Server sichtbar als <code>[NAME]</code>-Abschnitt " +
|
|
211
|
+
"in <code>/etc/samba/smb.conf</code> oder unter Windows als Freigabe-Name. " +
|
|
212
|
+
"Beispiele: <code>daten</code>, <code>public</code>, <code>projekte</code>. " +
|
|
213
|
+
"Nicht die Ordner-Bezeichnung, sondern der Freigabe-Name eingeben.",
|
|
76
214
|
type: "String",
|
|
77
215
|
required: true,
|
|
78
216
|
}),
|
|
79
|
-
new Field({
|
|
80
|
-
|
|
81
|
-
|
|
217
|
+
new Field({
|
|
218
|
+
name: "domain",
|
|
219
|
+
label: "Domäne / Arbeitsgruppe",
|
|
220
|
+
sublabel:
|
|
221
|
+
"Windows-Domäne oder Arbeitsgruppe des Benutzers. " +
|
|
222
|
+
"Für klassische Samba-Server im Heim-/Firmennetz meist <code>WORKGROUP</code> (Standard). " +
|
|
223
|
+
"Für Active Directory: NetBIOS-Name der Domäne, z. B. <code>CONTOSO</code>. Nicht der FQDN.",
|
|
224
|
+
type: "String",
|
|
225
|
+
default: "WORKGROUP",
|
|
226
|
+
}),
|
|
227
|
+
new Field({
|
|
228
|
+
name: "username",
|
|
229
|
+
label: "Benutzername",
|
|
230
|
+
sublabel:
|
|
231
|
+
"Samba-/AD-Benutzer, unter dem gelesen und (je nach Rechten) geschrieben wird. " +
|
|
232
|
+
"<b>Nicht</b> im Format <code>DOMÄNE\\user</code> – die Domäne gehört oben ins eigene Feld. " +
|
|
233
|
+
"Leer lassen für anonymen Zugriff (nur möglich, wenn der Share <code>guest ok = yes</code> erlaubt).",
|
|
234
|
+
type: "String",
|
|
235
|
+
}),
|
|
236
|
+
new Field({
|
|
237
|
+
name: "password",
|
|
238
|
+
label: "Passwort",
|
|
239
|
+
sublabel:
|
|
240
|
+
"Passwort des Samba-Benutzers. Wird verschlüsselt in der Saltcorn-Datenbank abgelegt. " +
|
|
241
|
+
"<b>Wichtig:</b> Samba nutzt ein eigenes Passwort (<code>smbpasswd</code>), nicht zwingend das Linux-Login-Passwort. " +
|
|
242
|
+
"Moderne Samba-Server lehnen leere Passwörter ab.",
|
|
243
|
+
type: "String",
|
|
244
|
+
input_type: "password",
|
|
245
|
+
}),
|
|
82
246
|
new Field({
|
|
83
247
|
name: "base_path",
|
|
84
|
-
label: "
|
|
85
|
-
sublabel:
|
|
248
|
+
label: "Basispfad (optional)",
|
|
249
|
+
sublabel:
|
|
250
|
+
"Beschränkt sämtlichen Zugriff auf ein Unterverzeichnis der Freigabe. " +
|
|
251
|
+
"Relativ, mit Schrägstrichen, <b>ohne</b> führenden Slash. " +
|
|
252
|
+
"Beispiel: <code>projekte/2026</code> → der File-Manager sieht nur diesen Unterordner und alles darin. " +
|
|
253
|
+
"Leer lassen für die komplette Share-Wurzel. <code>..</code> und absolute Pfade werden abgelehnt.",
|
|
86
254
|
type: "String",
|
|
87
255
|
}),
|
|
88
|
-
new Field({
|
|
256
|
+
new Field({
|
|
257
|
+
name: "port",
|
|
258
|
+
label: "TCP-Port",
|
|
259
|
+
sublabel:
|
|
260
|
+
"SMB-Port des Servers. Standard: <code>445</code> (SMB2/3 über TCP). " +
|
|
261
|
+
"Nur ändern, wenn Ihr Server bewusst auf einem anderen Port läuft. " +
|
|
262
|
+
"<b>SMBv1 (Port 139/NetBIOS) wird nicht unterstützt</b> – aktivieren Sie SMB2+ auf dem Server " +
|
|
263
|
+
"(<code>min protocol = SMB2</code> in smb.conf).",
|
|
264
|
+
type: "Integer",
|
|
265
|
+
default: 445,
|
|
266
|
+
}),
|
|
267
|
+
new Field({
|
|
268
|
+
name: "_test_html",
|
|
269
|
+
label: " ",
|
|
270
|
+
input_type: "custom_html",
|
|
271
|
+
attributes: { html: CONNECTION_TEST_HTML },
|
|
272
|
+
}),
|
|
89
273
|
],
|
|
90
274
|
}),
|
|
91
275
|
},
|
|
92
276
|
{
|
|
93
|
-
name: "
|
|
277
|
+
name: "Zugriff & Berechtigungen",
|
|
94
278
|
form: () =>
|
|
95
279
|
new Form({
|
|
96
280
|
fields: [
|
|
97
281
|
new Field({
|
|
98
282
|
name: "min_role_read",
|
|
99
|
-
label: "
|
|
100
|
-
sublabel:
|
|
283
|
+
label: "Mindestrolle für Lesen",
|
|
284
|
+
sublabel:
|
|
285
|
+
"Saltcorn-Rolle, die mindestens nötig ist, um Dateien und Verzeichnisse anzusehen. " +
|
|
286
|
+
"Werte: <code>1</code>=Admin, <code>40</code>=Staff, <code>80</code>=User, <code>100</code>=Public. " +
|
|
287
|
+
"Kleinere Zahl = höhere Rolle. Standard: <code>80</code> (alle angemeldeten Benutzer).",
|
|
101
288
|
type: "Integer",
|
|
102
289
|
default: 80,
|
|
103
290
|
}),
|
|
104
291
|
new Field({
|
|
105
292
|
name: "min_role_write",
|
|
106
|
-
label: "
|
|
293
|
+
label: "Mindestrolle für Upload / Löschen / Umbenennen",
|
|
107
294
|
sublabel:
|
|
108
|
-
"
|
|
295
|
+
"Setzen Sie auf <code>100</code>, um <b>alle</b> Schreibaktionen komplett zu deaktivieren – auch wenn die " +
|
|
296
|
+
"Checkboxen unten aktiv sind. Standard: <code>40</code> (nur Staff und Admin).",
|
|
109
297
|
type: "Integer",
|
|
110
298
|
default: 40,
|
|
111
299
|
}),
|
|
112
300
|
new Field({
|
|
113
301
|
name: "allow_upload",
|
|
114
|
-
label: "
|
|
302
|
+
label: "Datei-Upload erlauben",
|
|
303
|
+
sublabel: "Zeigt den Upload-Button und aktiviert POST /sambaupload.",
|
|
115
304
|
type: "Bool",
|
|
116
305
|
default: false,
|
|
117
306
|
}),
|
|
118
307
|
new Field({
|
|
119
308
|
name: "allow_delete",
|
|
120
|
-
label: "
|
|
309
|
+
label: "Löschen erlauben",
|
|
310
|
+
sublabel: "Erlaubt das Löschen von Dateien und leeren Verzeichnissen.",
|
|
121
311
|
type: "Bool",
|
|
122
312
|
default: false,
|
|
123
313
|
}),
|
|
124
314
|
new Field({
|
|
125
315
|
name: "allow_rename",
|
|
126
|
-
label: "
|
|
316
|
+
label: "Umbenennen / Verschieben erlauben",
|
|
317
|
+
sublabel: "Erlaubt Umbenennen und Verschieben innerhalb des Basispfads.",
|
|
127
318
|
type: "Bool",
|
|
128
319
|
default: false,
|
|
129
320
|
}),
|
|
130
321
|
new Field({
|
|
131
322
|
name: "allow_mkdir",
|
|
132
|
-
label: "
|
|
323
|
+
label: "Neue Verzeichnisse anlegen erlauben",
|
|
324
|
+
sublabel: "Zeigt den „Neuer Ordner“-Button in der File-Manager-Ansicht.",
|
|
133
325
|
type: "Bool",
|
|
134
326
|
default: false,
|
|
135
327
|
}),
|
|
136
328
|
new Field({
|
|
137
329
|
name: "max_upload_mb",
|
|
138
|
-
label: "Max
|
|
330
|
+
label: "Max. Upload-Größe pro Datei (MiB)",
|
|
331
|
+
sublabel: "Serverseitige Obergrenze pro Datei. Standard: 50 MiB.",
|
|
139
332
|
type: "Integer",
|
|
140
333
|
default: 50,
|
|
141
334
|
}),
|
|
142
335
|
new Field({
|
|
143
336
|
name: "denied_extensions",
|
|
144
|
-
label: "
|
|
337
|
+
label: "Gesperrte Datei-Endungen (komma-getrennt, ohne Punkte)",
|
|
145
338
|
sublabel:
|
|
146
|
-
"
|
|
339
|
+
"Standard: <code>exe,bat,cmd,com,msi,scr,vbs,js,jse,wsf,wsh,ps1,ps1xml,psm1,sh,bash,zsh</code>. " +
|
|
340
|
+
"Leer lassen für die Standard-Liste.",
|
|
147
341
|
type: "String",
|
|
148
342
|
}),
|
|
149
343
|
new Field({
|
|
150
344
|
name: "public_smb_host",
|
|
151
|
-
label: "
|
|
345
|
+
label: "Für Clients sichtbarer SMB-Hostname (optional)",
|
|
152
346
|
sublabel:
|
|
153
|
-
"
|
|
347
|
+
"Wird in erzeugten <code>smb://</code>-Links verwendet. Standard: der Wert aus dem Feld <b>Server</b>. " +
|
|
348
|
+
"Nützlich, wenn Saltcorn in Docker läuft (Server = interne IP), Clients aber den LAN-Namen sehen sollen " +
|
|
349
|
+
"(z. B. <code>fileserver.local</code>).",
|
|
154
350
|
type: "String",
|
|
155
351
|
}),
|
|
156
352
|
],
|
|
@@ -369,6 +565,118 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
|
|
|
369
565
|
},
|
|
370
566
|
},
|
|
371
567
|
|
|
568
|
+
// ---- Connection test (admin only) --------------------------------------
|
|
569
|
+
//
|
|
570
|
+
// POST /sambatest
|
|
571
|
+
// Body (JSON): { server, share, domain, username, password, base_path, port }
|
|
572
|
+
// Returns: { ok, server, share, base_path, entries?: [{name,isDirectory}],
|
|
573
|
+
// error?, code?, hint? }
|
|
574
|
+
//
|
|
575
|
+
// The route accepts values straight from the config form so admins can
|
|
576
|
+
// verify the connection BEFORE saving. It never touches the persisted
|
|
577
|
+
// configuration and is restricted to role_id === 1 (admin).
|
|
578
|
+
{
|
|
579
|
+
url: "/sambatest",
|
|
580
|
+
method: "post",
|
|
581
|
+
// The route is protected by an explicit admin check in the handler
|
|
582
|
+
// (roleOf(req) !== 1 returns 403), so the standard CSRF middleware
|
|
583
|
+
// would only add friction without adding security here — the token
|
|
584
|
+
// hidden field is not in scope of an arbitrary attacker who cannot
|
|
585
|
+
// already run scripts inside an admin's browser (which would defeat
|
|
586
|
+
// any web-app anyway). Setting noCsrf keeps the button working even
|
|
587
|
+
// when the config-page CSRF token has already been consumed by the
|
|
588
|
+
// save-workflow or when a stricter CSRF policy is applied.
|
|
589
|
+
noCsrf: true,
|
|
590
|
+
callback: async ({ req, res }) => {
|
|
591
|
+
if (roleOf(req) !== 1) {
|
|
592
|
+
return jsonError(res, 403, "Only admins can test the connection.");
|
|
593
|
+
}
|
|
594
|
+
// Accept both JSON and URL-encoded bodies. express.json and
|
|
595
|
+
// express.urlencoded are both installed globally by Saltcorn.
|
|
596
|
+
const body = (req.body && typeof req.body === "object") ? req.body : {};
|
|
597
|
+
const testCfg = {
|
|
598
|
+
server: String(body.server || "").trim(),
|
|
599
|
+
share: String(body.share || "").trim(),
|
|
600
|
+
domain: String(body.domain || "").trim() || "WORKGROUP",
|
|
601
|
+
username: String(body.username || "").trim(),
|
|
602
|
+
password: String(body.password || ""),
|
|
603
|
+
base_path: String(body.base_path || "").trim(),
|
|
604
|
+
port: Number(body.port) || 445,
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
if (!testCfg.server) return jsonError(res, 400, "Please enter a Server (hostname or IP).");
|
|
608
|
+
if (!testCfg.share) return jsonError(res, 400, "Please enter a Share name.");
|
|
609
|
+
|
|
610
|
+
const started = Date.now();
|
|
611
|
+
try {
|
|
612
|
+
const listing = await withClient(testCfg, async (client) => {
|
|
613
|
+
// If base_path is set, list it — that also verifies traversal.
|
|
614
|
+
const rel = testCfg.base_path ? sanitizeRelativePath(testCfg.base_path) : "";
|
|
615
|
+
return await client.readdir(rel);
|
|
616
|
+
});
|
|
617
|
+
const took = Date.now() - started;
|
|
618
|
+
return res.json({
|
|
619
|
+
ok: true,
|
|
620
|
+
server: testCfg.server,
|
|
621
|
+
share: testCfg.share,
|
|
622
|
+
base_path: testCfg.base_path || "(share root)",
|
|
623
|
+
port: testCfg.port,
|
|
624
|
+
domain: testCfg.domain,
|
|
625
|
+
username: testCfg.username || "(anonymous)",
|
|
626
|
+
duration_ms: took,
|
|
627
|
+
entry_count: Array.isArray(listing) ? listing.length : 0,
|
|
628
|
+
entries: (Array.isArray(listing) ? listing : []).slice(0, 20).map((e) => ({
|
|
629
|
+
name: e && (e.name || e),
|
|
630
|
+
isDirectory: !!(e && (e.isDirectory === true || (e.stats && e.stats.isDirectory && e.stats.isDirectory()))),
|
|
631
|
+
})),
|
|
632
|
+
truncated: Array.isArray(listing) && listing.length > 20,
|
|
633
|
+
});
|
|
634
|
+
} catch (err) {
|
|
635
|
+
// Turn opaque SMB / socket errors into actionable hints.
|
|
636
|
+
const msg = String((err && err.message) || err || "Unknown error");
|
|
637
|
+
const code = (err && (err.code || err.errno)) || null;
|
|
638
|
+
let hint = null;
|
|
639
|
+
const m = msg.toLowerCase();
|
|
640
|
+
if (code === "ECONNREFUSED" || m.includes("econnrefused"))
|
|
641
|
+
hint = "The server refused the connection on port " + testCfg.port +
|
|
642
|
+
". Check that Samba is running and that a firewall (or Docker) does not block TCP/445.";
|
|
643
|
+
else if (code === "ETIMEDOUT" || m.includes("etimedout") || m.includes("timed out"))
|
|
644
|
+
hint = "Timeout — the host did not respond. Verify Server address and that it is reachable from the Saltcorn host (try: ping / nc -vz " + testCfg.server + " " + testCfg.port + ").";
|
|
645
|
+
else if (code === "ENOTFOUND" || code === "EAI_AGAIN" || m.includes("getaddrinfo"))
|
|
646
|
+
hint = "DNS lookup failed for '" + testCfg.server + "'. Use an IP address or make sure the hostname resolves from inside the Saltcorn container.";
|
|
647
|
+
else if (code === "EHOSTUNREACH" || m.includes("ehostunreach"))
|
|
648
|
+
hint = "No route to host. Check network / VPN / bridged Docker network.";
|
|
649
|
+
else if (m.includes("logon_failure") || m.includes("nt_status_logon_failure") || m.includes("access_denied") || m.includes("nt_status_access_denied"))
|
|
650
|
+
hint = "Login rejected. Check Username, Password and Domain / Workgroup. On modern Samba servers, empty passwords are usually disabled.";
|
|
651
|
+
else if (m.includes("bad_network_name") || m.includes("nt_status_bad_network_name"))
|
|
652
|
+
hint = "The share '" + testCfg.share + "' does not exist on " + testCfg.server + ". Check spelling and the [share] section in smb.conf.";
|
|
653
|
+
else if (m.includes("account_disabled") || m.includes("account_locked"))
|
|
654
|
+
hint = "The user account is disabled or locked on the Samba server.";
|
|
655
|
+
else if (m.includes("password_expired") || m.includes("password_must_change"))
|
|
656
|
+
hint = "The password must be changed before this account can be used.";
|
|
657
|
+
else if (m.includes("smb1") || m.includes("protocol"))
|
|
658
|
+
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).";
|
|
659
|
+
else if (m.includes("traversal") || m.includes("path"))
|
|
660
|
+
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 '..'.";
|
|
661
|
+
|
|
662
|
+
return res.status(200).json({
|
|
663
|
+
ok: false,
|
|
664
|
+
error: msg,
|
|
665
|
+
code,
|
|
666
|
+
hint,
|
|
667
|
+
attempted: {
|
|
668
|
+
server: testCfg.server,
|
|
669
|
+
share: testCfg.share,
|
|
670
|
+
port: testCfg.port,
|
|
671
|
+
domain: testCfg.domain,
|
|
672
|
+
username: testCfg.username || "(anonymous)",
|
|
673
|
+
base_path: testCfg.base_path || "(share root)",
|
|
674
|
+
},
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
},
|
|
678
|
+
},
|
|
679
|
+
|
|
372
680
|
// ---- Write routes (v0.3.0) --------------------------------------------
|
|
373
681
|
|
|
374
682
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "saltcorn-samba",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
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": {
|