From 6362f1b9f7c92a272ccf077cc2ded2e137b9d56a Mon Sep 17 00:00:00 2001
From: "a.gritsenko"
Date: Fri, 2 Oct 2026 23:50:12 +0300
Subject: [PATCH] SMB: map to guest = Never when no share allows guests
With "Bad User" an unknown account (the Windows login) silently became a
guest session; Windows 10/11 refuse guest sessions and report "network
path not found" instead of asking for a password, so password-only shares
were unreachable from Explorer. Map to guest only while a guest share
exists, and document the net use workaround on the shares page.
Co-Authored-By: Claude Opus 5.5
---
internal/shares/render.go | 18 +++++++++++++++++-
internal/shares/shares_test.go | 6 ++++++
internal/web/templates/shares.html | 3 ++-
3 files changed, 25 insertions(+), 2 deletions(-)
diff --git a/internal/shares/render.go b/internal/shares/render.go
index e5a578e..06d6aa8 100644
--- a/internal/shares/render.go
+++ b/internal/shares/render.go
@@ -15,18 +15,34 @@ func yesno(b bool) string {
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
// SMB session acts as (force user), so files end up with the same owner the
// containers use.
func RenderSMB(c Config, owner string) string {
var b strings.Builder
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"
+ }
g := []string{
"workgroup = " + c.SMB.Workgroup,
"server string = " + c.SMB.ServerString,
"server role = standalone server",
"security = user",
- "map to guest = Bad User",
+ "map to guest = " + mapGuest,
"guest account = nobody",
"server min protocol = SMB2",
"unix charset = UTF-8",
diff --git a/internal/shares/shares_test.go b/internal/shares/shares_test.go
index de484bb..e117666 100644
--- a/internal/shares/shares_test.go
+++ b/internal/shares/shares_test.go
@@ -40,6 +40,12 @@ func TestRenderSMB(t *testing.T) {
if strings.Count(out, "\n veto files = ") != 1 {
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)
+ }
}
func TestRenderExports(t *testing.T) {
diff --git a/internal/web/templates/shares.html b/internal/web/templates/shares.html
index 821cd92..9ef1c3b 100644
--- a/internal/web/templates/shares.html
+++ b/internal/web/templates/shares.html
@@ -80,7 +80,8 @@
Windows: \\{{.Host}}\share in Explorer, or map a network drive. macOS: Finder → Go → Connect to Server → smb://{{.Host}}/share.
- Note: Windows 10/11 Pro/Enterprise refuse guest 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).
+ Note: Windows 10/11 Pro/Enterprise refuse guest 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: net use \\{{.Host}}\share /user:<name> * (Windows keeps one login per server — run net use * /delete first).