Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b965d2c9db | ||
|
|
6362f1b9f7 |
@@ -28,6 +28,7 @@ type SMBConfig struct {
|
|||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
Workgroup string `json:"workgroup"`
|
Workgroup string `json:"workgroup"`
|
||||||
ServerString string `json:"server_string"`
|
ServerString string `json:"server_string"`
|
||||||
|
Legacy bool `json:"legacy"` // allow SMB1 + NTLMv1 (IPMI/BMC virtual media, old devices)
|
||||||
Shares []SMBShare `json:"shares"`
|
Shares []SMBShare `json:"shares"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,20 +15,42 @@ func yesno(b bool) string {
|
|||||||
return "no"
|
return "no"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func anyGuest(c Config) bool {
|
||||||
|
for _, s := range c.SMB.Shares {
|
||||||
|
if s.Guest {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// RenderSMB produces the complete smb.conf. owner is the Unix account every
|
// RenderSMB produces the complete smb.conf. owner is the Unix account every
|
||||||
// SMB session acts as (force user), so files end up with the same owner the
|
// SMB session acts as (force user), so files end up with the same owner the
|
||||||
// containers use.
|
// containers use.
|
||||||
func RenderSMB(c Config, owner string) string {
|
func RenderSMB(c Config, owner string) string {
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
fmt.Fprintf(&b, "%s\n\n[global]\n", Header)
|
fmt.Fprintf(&b, "%s\n\n[global]\n", Header)
|
||||||
|
// Bad User turns unknown accounts (e.g. the Windows login) into guests.
|
||||||
|
// Without guest shares that only yields a guest session Windows refuses
|
||||||
|
// with a misleading "network path not found" instead of a password prompt.
|
||||||
|
mapGuest := "Never"
|
||||||
|
if anyGuest(c) {
|
||||||
|
mapGuest = "Bad User"
|
||||||
|
}
|
||||||
|
minProto := "server min protocol = SMB2"
|
||||||
|
if c.SMB.Legacy {
|
||||||
|
// old embedded clients (e.g. Supermicro BMC virtual media) only offer
|
||||||
|
// SMB1 "NT LM 0.12" and authenticate with NTLMv1
|
||||||
|
minProto = "server min protocol = NT1\n ntlm auth = ntlmv1-permitted"
|
||||||
|
}
|
||||||
g := []string{
|
g := []string{
|
||||||
"workgroup = " + c.SMB.Workgroup,
|
"workgroup = " + c.SMB.Workgroup,
|
||||||
"server string = " + c.SMB.ServerString,
|
"server string = " + c.SMB.ServerString,
|
||||||
"server role = standalone server",
|
"server role = standalone server",
|
||||||
"security = user",
|
"security = user",
|
||||||
"map to guest = Bad User",
|
"map to guest = " + mapGuest,
|
||||||
"guest account = nobody",
|
"guest account = nobody",
|
||||||
"server min protocol = SMB2",
|
minProto,
|
||||||
"unix charset = UTF-8",
|
"unix charset = UTF-8",
|
||||||
"logging = file",
|
"logging = file",
|
||||||
"log file = /var/log/samba/log.%m",
|
"log file = /var/log/samba/log.%m",
|
||||||
|
|||||||
@@ -40,6 +40,19 @@ func TestRenderSMB(t *testing.T) {
|
|||||||
if strings.Count(out, "\n veto files = ") != 1 {
|
if strings.Count(out, "\n veto files = ") != 1 {
|
||||||
t.Errorf("veto files only where requested")
|
t.Errorf("veto files only where requested")
|
||||||
}
|
}
|
||||||
|
// without guest shares unknown users are rejected, so Windows asks for a password
|
||||||
|
c := sample()
|
||||||
|
c.SMB.Shares = c.SMB.Shares[1:]
|
||||||
|
if out := RenderSMB(c, "ansible"); !strings.Contains(out, "map to guest = Never") || strings.Contains(out, "Bad User") {
|
||||||
|
t.Errorf("user-only config must not map to guest:\n%s", out)
|
||||||
|
}
|
||||||
|
if out := RenderSMB(c, "ansible"); strings.Contains(out, "NT1") || strings.Contains(out, "ntlmv1") {
|
||||||
|
t.Errorf("SMB1 enabled without the legacy flag")
|
||||||
|
}
|
||||||
|
c.SMB.Legacy = true
|
||||||
|
if out := RenderSMB(c, "ansible"); !strings.Contains(out, "\n server min protocol = NT1\n ntlm auth = ntlmv1-permitted\n") || strings.Contains(out, "SMB2") {
|
||||||
|
t.Errorf("legacy mode:\n%s", out)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderExports(t *testing.T) {
|
func TestRenderExports(t *testing.T) {
|
||||||
|
|||||||
@@ -204,6 +204,7 @@ func (s *Server) smbSettings(w http.ResponseWriter, r *http.Request) {
|
|||||||
c.SMB.Enabled = r.FormValue("enabled") != ""
|
c.SMB.Enabled = r.FormValue("enabled") != ""
|
||||||
c.SMB.Workgroup = strings.ToUpper(strings.TrimSpace(r.FormValue("workgroup")))
|
c.SMB.Workgroup = strings.ToUpper(strings.TrimSpace(r.FormValue("workgroup")))
|
||||||
c.SMB.ServerString = strings.TrimSpace(r.FormValue("server_string"))
|
c.SMB.ServerString = strings.TrimSpace(r.FormValue("server_string"))
|
||||||
|
c.SMB.Legacy = r.FormValue("legacy") != ""
|
||||||
if err := shares.ValidateComment(c.SMB.ServerString); err != nil {
|
if err := shares.ValidateComment(c.SMB.ServerString); err != nil {
|
||||||
s.sharesFail(w, r, "Not saved: server description: "+err.Error(), smbForm{}, nfsForm{})
|
s.sharesFail(w, r, "Not saved: server description: "+err.Error(), smbForm{}, nfsForm{})
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -226,3 +226,24 @@ func TestSharesNFSFlow(t *testing.T) {
|
|||||||
t.Errorf("delete: %d %v", rec.Code, f.calls)
|
t.Errorf("delete: %d %v", rec.Code, f.calls)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSharesSMBLegacySetting(t *testing.T) {
|
||||||
|
srv, _, _ := newTestServer(t)
|
||||||
|
f := &shareRunner{users: "alice:1001:\n"}
|
||||||
|
withShareRunner(srv, f)
|
||||||
|
postForm(srv, "/shares/smb/save", url.Values{"name": {"iso"}, "path": {"/tank/data"}, "users": {"alice"}, "browseable": {"1"}})
|
||||||
|
rec := postForm(srv, "/shares/smb/settings", url.Values{"enabled": {"1"}, "workgroup": {"workgroup"}, "server_string": {"NAS"}, "legacy": {"1"}})
|
||||||
|
if rec.Code != http.StatusSeeOther || !srv.shares.Get().SMB.Legacy {
|
||||||
|
t.Fatalf("legacy not saved: %d %s", rec.Code, rec.Body.String())
|
||||||
|
}
|
||||||
|
if conf := f.written(shares.DefaultSMBConf); !strings.Contains(conf, "server min protocol = NT1") {
|
||||||
|
t.Errorf("smb.conf without SMB1:\n%s", conf)
|
||||||
|
}
|
||||||
|
if !strings.Contains(get(srv, "/shares").Body.String(), `name="legacy" value="1" checked`) {
|
||||||
|
t.Errorf("legacy checkbox not shown as checked")
|
||||||
|
}
|
||||||
|
postForm(srv, "/shares/smb/settings", url.Values{"enabled": {"1"}, "workgroup": {"WORKGROUP"}, "server_string": {"NAS"}})
|
||||||
|
if srv.shares.Get().SMB.Legacy || !strings.Contains(f.written(shares.DefaultSMBConf), "server min protocol = SMB2") {
|
||||||
|
t.Errorf("legacy not cleared")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
<label class="check"><input type="checkbox" name="enabled" value="1" {{if .C.SMB.Enabled}}checked{{end}}> SMB server enabled</label>
|
<label class="check"><input type="checkbox" name="enabled" value="1" {{if .C.SMB.Enabled}}checked{{end}}> SMB server enabled</label>
|
||||||
<label>Workgroup <input name="workgroup" value="{{.C.SMB.Workgroup}}"></label>
|
<label>Workgroup <input name="workgroup" value="{{.C.SMB.Workgroup}}"></label>
|
||||||
<label>Description <input name="server_string" value="{{.C.SMB.ServerString}}"></label>
|
<label>Description <input name="server_string" value="{{.C.SMB.ServerString}}"></label>
|
||||||
|
<label class="check"><input type="checkbox" name="legacy" value="1" {{if .C.SMB.Legacy}}checked{{end}}> Allow legacy clients (SMB1 + NTLMv1) — IPMI/BMC virtual media, old devices; less secure</label>
|
||||||
<div class="field"><span class="field-name"> </span><span><button type="submit" class="secondary">Save SMB settings</button></span></div>
|
<div class="field"><span class="field-name"> </span><span><button type="submit" class="secondary">Save SMB settings</button></span></div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@@ -80,7 +81,8 @@
|
|||||||
<div class="field"><span class="field-name"> </span><span><button type="submit" class="secondary">Add user / set password</button></span></div>
|
<div class="field"><span class="field-name"> </span><span><button type="submit" class="secondary">Add user / set password</button></span></div>
|
||||||
</form>
|
</form>
|
||||||
<p class="muted small">Windows: <span class="mono">\\{{.Host}}\share</span> in Explorer, or map a network drive. macOS: Finder → Go → Connect to Server → <span class="mono">smb://{{.Host}}/share</span>.
|
<p class="muted small">Windows: <span class="mono">\\{{.Host}}\share</span> in Explorer, or map a network drive. macOS: Finder → Go → Connect to Server → <span class="mono">smb://{{.Host}}/share</span>.
|
||||||
Note: Windows 10/11 Pro/Enterprise refuse <i>guest</i> shares by default (error 0x80070035 «network path not found») — use an SMB user with a password, or allow guest access on the PC (gpedit: Lanman Workstation → Enable insecure guest logons).</p>
|
Note: Windows 10/11 Pro/Enterprise refuse <i>guest</i> shares by default (error 0x80070035 «network path not found») — use an SMB user with a password, or allow guest access on the PC (gpedit: Lanman Workstation → Enable insecure guest logons).
|
||||||
|
If a password share still gives 0x80070035, connect explicitly: <span class="mono">net use \\{{.Host}}\share /user:<name> *</span> (Windows keeps one login per server — run <span class="mono">net use * /delete</span> first).</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card" id="nfs">
|
<section class="card" id="nfs">
|
||||||
|
|||||||
+3
-1
@@ -36,7 +36,9 @@ also after resolving symlinks. Definitions are stored in
|
|||||||
/etc/samba/smb.conf (whole file, header "Managed by nasmon") and
|
/etc/samba/smb.conf (whole file, header "Managed by nasmon") and
|
||||||
/etc/exports.d/nasmon.exports, validates with testparm, then reloads smbd /
|
/etc/exports.d/nasmon.exports, validates with testparm, then reloads smbd /
|
||||||
exportfs -ra. A rejected config is rolled back to the .bak copy. Daemons are
|
exportfs -ra. A rejected config is rolled back to the .bak copy. Daemons are
|
||||||
enabled while at least one share exists and disabled otherwise.
|
enabled while at least one share exists and disabled otherwise. "Allow legacy
|
||||||
|
clients" in the SMB settings turns on SMB1 + NTLMv1 (server min protocol = NT1)
|
||||||
|
for IPMI/BMC virtual media and other old devices; it is off by default.
|
||||||
|
|
||||||
Alerts: the × on an open alert acknowledges it (hidden and not mailed until the
|
Alerts: the × on an open alert acknowledges it (hidden and not mailed until the
|
||||||
problem changes or clears); the × on a resolved alert removes it from the log.
|
problem changes or clears); the × on a resolved alert removes it from the log.
|
||||||
|
|||||||
Reference in New Issue
Block a user