This commit is contained in:
parent
8d09a1ad49
commit
038f425f2d
@ -113,6 +113,16 @@ func TestSmailServiceUsesOnlyBindServiceCapability(t *testing.T) {
|
||||
t.Fatalf("smail unit is missing %q", required)
|
||||
}
|
||||
}
|
||||
usesEnvironmentFile := false
|
||||
for _, line := range strings.Split(content, "\n") {
|
||||
if strings.TrimSpace(line) == "EnvironmentFile=/etc/smail/smail.env" {
|
||||
usesEnvironmentFile = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !usesEnvironmentFile {
|
||||
t.Fatal("smail unit must define EnvironmentFile as a standalone directive")
|
||||
}
|
||||
if strings.Contains(content, "User=root") {
|
||||
t.Fatal("smail unit must not run as root")
|
||||
}
|
||||
|
||||
@ -150,6 +150,7 @@ func TestProtocolAuthenticationAcceptsConfiguredFullEmailIdentifier(t *testing.T
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
protocolCodes := make(map[string]string)
|
||||
for _, protocol := range []string{"smtp", "imap", "pop3"} {
|
||||
if err := service.SetProtocolEnabled(user.ID, protocol, true); err != nil {
|
||||
t.Fatalf("enable %s: %v", protocol, err)
|
||||
@ -158,6 +159,7 @@ func TestProtocolAuthenticationAcceptsConfiguredFullEmailIdentifier(t *testing.T
|
||||
if err != nil {
|
||||
t.Fatalf("generate %s code: %v", protocol, err)
|
||||
}
|
||||
protocolCodes[protocol] = code
|
||||
got, err := AuthenticateProtocol(protocol, "192.0.2.10:12345", " Alice@Example.com ", code)
|
||||
if err != nil {
|
||||
t.Fatalf("%s full email authentication failed: %v", protocol, err)
|
||||
@ -170,10 +172,10 @@ func TestProtocolAuthenticationAcceptsConfiguredFullEmailIdentifier(t *testing.T
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := AuthenticateProtocol("imap", "192.0.2.10:12345", "alice", "CorrectP12"); err == nil {
|
||||
if _, err := AuthenticateProtocol("imap", "192.0.2.10:12345", "alice", protocolCodes["imap"]); err == nil {
|
||||
t.Fatal("account-only protocol login should be rejected")
|
||||
}
|
||||
if _, err := AuthenticateProtocol("imap", "192.0.2.10:12345", "alice@other.example", "CorrectP12"); err == nil {
|
||||
if _, err := AuthenticateProtocol("imap", "192.0.2.10:12345", "alice@other.example", protocolCodes["imap"]); err == nil {
|
||||
t.Fatal("unconfigured email domain should be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,6 +52,20 @@ func NormalizeIdentifierKey(identifier string) (string, error) {
|
||||
return normalized, err
|
||||
}
|
||||
|
||||
// NormalizeProtocolIdentifier 仅接受属于已配置邮箱域名的完整地址,供 SMTP、IMAP、POP3 客户端认证使用。
|
||||
// Web 登录仍可使用短账号或已验证的外部邮箱,不能复用到邮件协议认证。
|
||||
func NormalizeProtocolIdentifier(identifier string) (string, error) {
|
||||
normalized, kind, err := parseLoginIdentifier(identifier)
|
||||
if err != nil || kind != IdentifierEmail {
|
||||
return "", errors.New("invalid identifier")
|
||||
}
|
||||
parts := strings.SplitN(normalized, "@", 2)
|
||||
if len(parts) != 2 || !isConfiguredLoginDomain(parts[1]) {
|
||||
return "", errors.New("invalid identifier")
|
||||
}
|
||||
return normalized, nil
|
||||
}
|
||||
|
||||
// ResolveIdentifier 将账号或已验证邮箱绑定解析为唯一用户身份。
|
||||
func ResolveIdentifier(identifier string) (ResolvedIdentity, error) {
|
||||
normalized, kind, err := parseLoginIdentifier(identifier)
|
||||
|
||||
@ -93,7 +93,6 @@ func TestNormalizeIdentifierKeepsLegacyAccountCompatibility(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestResolveIdentifierFallbackToConfiguredDomainUser(t *testing.T) {
|
||||
oldConfig := config.Instance
|
||||
oldDB := db.Instance
|
||||
@ -106,6 +105,14 @@ func TestResolveIdentifierFallbackToConfiguredDomainUser(t *testing.T) {
|
||||
if err := db.Init(""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, err := NormalizeProtocolIdentifier(" Bob@Solit.cn "); err != nil || got != "bob@solit.cn" {
|
||||
t.Fatalf("normalized protocol identifier = %q, err = %v", got, err)
|
||||
}
|
||||
for _, identifier := range []string{"bob", "bob@other.com"} {
|
||||
if _, err := NormalizeProtocolIdentifier(identifier); err == nil {
|
||||
t.Fatalf("protocol identifier %q was accepted", identifier)
|
||||
}
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if db.Instance != nil && db.Instance != oldDB {
|
||||
_ = db.Instance.Close()
|
||||
@ -141,4 +148,4 @@ func TestResolveIdentifierFallbackToConfiguredDomainUser(t *testing.T) {
|
||||
if _, err := ResolveIdentifier("bob@other.com"); err == nil {
|
||||
t.Fatal("unconfigured domain must not be resolved")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package auth
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
@ -46,7 +46,7 @@ var (
|
||||
func AuthenticateProtocol(protocol string, remoteAddress string, account string, plain string) (*models.User, error) {
|
||||
normalizedProtocol, protocolErr := credentials.NormalizeProtocol(protocol)
|
||||
normalizedAccount := strings.ToLower(strings.TrimSpace(account))
|
||||
if normalizedIdentifier, err := NormalizeIdentifierKey(account); err == nil {
|
||||
if normalizedIdentifier, err := NormalizeProtocolIdentifier(account); err == nil {
|
||||
normalizedAccount = normalizedIdentifier
|
||||
}
|
||||
normalizedRemote := protocolRemoteHost(remoteAddress)
|
||||
@ -67,19 +67,15 @@ func AuthenticateProtocol(protocol string, remoteAddress string, account string,
|
||||
return fail()
|
||||
}
|
||||
|
||||
identity, err := ResolveIdentifier(account)
|
||||
protocolIdentifier, err := NormalizeProtocolIdentifier(account)
|
||||
if err != nil {
|
||||
return fail()
|
||||
}
|
||||
identity, err := ResolveIdentifier(protocolIdentifier)
|
||||
if err != nil {
|
||||
return fail()
|
||||
}
|
||||
userID := identity.UserID
|
||||
if identity.Kind == IdentifierAccount {
|
||||
var u models.User
|
||||
has, err := db.Instance.Where("account = ? AND disabled = 0", identity.Account).Get(&u)
|
||||
if err != nil || !has || u.ID <= 0 {
|
||||
return fail()
|
||||
}
|
||||
userID = u.ID
|
||||
}
|
||||
if userID <= 0 {
|
||||
return fail()
|
||||
}
|
||||
|
||||
@ -84,9 +84,9 @@ if [ ! -e /etc/smail/smail.env ]; then
|
||||
# 只创建默认根目录;已存在的文件始终保留部署人员的自定义环境配置。
|
||||
umask 0077
|
||||
printf '%s\n' 'SMAIL_ROOT=/opt/smail' > /etc/smail/smail.env
|
||||
chown root:root /etc/smail/smail.env
|
||||
chmod 0600 /etc/smail/smail.env
|
||||
fi
|
||||
chown root:root /etc/smail/smail.env
|
||||
chmod 0600 /etc/smail/smail.env
|
||||
for directory in config data ssl log plugins; do
|
||||
target="$runtime_dir/$directory"
|
||||
# 内置插件在临时沙箱中展开,只兼容已存在的历史插件目录。
|
||||
|
||||
@ -7,6 +7,7 @@ Wants=network-online.target
|
||||
Type=simple
|
||||
User=smail
|
||||
Group=smail
|
||||
EnvironmentFile=/etc/smail/smail.env
|
||||
# 运行环境仅由 root 可读写的独立文件提供,避免在 unit 中混入部署环境配置。
|
||||
EnvironmentFile=/etc/smail/smail.env
|
||||
WorkingDirectory=/opt/smail
|
||||
|
||||
Loading…
Reference in New Issue
Block a user