完善tls配置
This commit is contained in:
@@ -2,6 +2,7 @@ package pop3_server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
@@ -66,7 +67,36 @@ func (s *POP3Server) StartTLS() error {
|
|||||||
if s.cfg.TLSCert == "" || s.cfg.TLSKey == "" {
|
if s.cfg.TLSCert == "" || s.cfg.TLSKey == "" {
|
||||||
return fmt.Errorf("POP3 TLS certificate or key not configured")
|
return fmt.Errorf("POP3 TLS certificate or key not configured")
|
||||||
}
|
}
|
||||||
return fmt.Errorf("POP3 TLS not yet implemented")
|
|
||||||
|
cert, err := tls.LoadX509KeyPair(s.cfg.TLSCert, s.cfg.TLSKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("load POP3 TLS certificate failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := tls.Listen("tcp", s.cfg.TLSAddr, &tls.Config{Certificates: []tls.Certificate{cert}})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("POP3 TLS listen failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("POP3 TLS server listening on %s", s.cfg.TLSAddr)
|
||||||
|
|
||||||
|
s.wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer s.wg.Done()
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer s.wg.Done()
|
||||||
|
s.handleConn(conn)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleConn handles a single POP3 client connection.
|
// handleConn handles a single POP3 client connection.
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ type DomainStore interface {
|
|||||||
Create(domain *db.Domain) error
|
Create(domain *db.Domain) error
|
||||||
GetByID(id uint) (*db.Domain, error)
|
GetByID(id uint) (*db.Domain, error)
|
||||||
GetByName(name string) (*db.Domain, error)
|
GetByName(name string) (*db.Domain, error)
|
||||||
|
GetFirstTLSEnabledWithCert() (*db.Domain, error)
|
||||||
Update(domain *db.Domain) error
|
Update(domain *db.Domain) error
|
||||||
Delete(id uint) error
|
Delete(id uint) error
|
||||||
List(page, size int) ([]db.Domain, int64, error)
|
List(page, size int) ([]db.Domain, int64, error)
|
||||||
@@ -49,6 +50,15 @@ func (s *domainStoreGorm) GetByName(name string) (*db.Domain, error) {
|
|||||||
return &domain, nil
|
return &domain, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFirstTLSEnabledWithCert retrieves the first TLS-enabled domain with certificate paths.
|
||||||
|
func (s *domainStoreGorm) GetFirstTLSEnabledWithCert() (*db.Domain, error) {
|
||||||
|
var domain db.Domain
|
||||||
|
if err := s.db.Where("tls_enabled = ? AND tls_cert_path <> ? AND tls_key_path <> ?", true, "", "").Order("id ASC").First(&domain).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &domain, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Update saves changes to an existing domain record.
|
// Update saves changes to an existing domain record.
|
||||||
func (s *domainStoreGorm) Update(domain *db.Domain) error {
|
func (s *domainStoreGorm) Update(domain *db.Domain) error {
|
||||||
return s.db.Save(domain).Error
|
return s.db.Save(domain).Error
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"mail_go/internal/db"
|
"mail_go/internal/db"
|
||||||
@@ -20,11 +24,12 @@ import (
|
|||||||
type AdminHandler struct {
|
type AdminHandler struct {
|
||||||
stores *store.Stores
|
stores *store.Stores
|
||||||
storage *storage.AttachmentStorage
|
storage *storage.AttachmentStorage
|
||||||
|
tlsDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAdminHandler creates a new AdminHandler with the given stores and attachment storage.
|
// NewAdminHandler creates a new AdminHandler with the given stores and attachment storage.
|
||||||
func NewAdminHandler(stores *store.Stores, attStorage *storage.AttachmentStorage) *AdminHandler {
|
func NewAdminHandler(stores *store.Stores, attStorage *storage.AttachmentStorage, tlsDir string) *AdminHandler {
|
||||||
return &AdminHandler{stores: stores, storage: attStorage}
|
return &AdminHandler{stores: stores, storage: attStorage, tlsDir: tlsDir}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dashboard renders the admin dashboard with summary statistics.
|
// Dashboard renders the admin dashboard with summary statistics.
|
||||||
@@ -64,17 +69,17 @@ func (h *AdminHandler) Dashboard(c *gin.Context) {
|
|||||||
currentUser, _ := c.Get("currentUser")
|
currentUser, _ := c.Get("currentUser")
|
||||||
|
|
||||||
c.HTML(200, "admin_dashboard", gin.H{
|
c.HTML(200, "admin_dashboard", gin.H{
|
||||||
"currentUser": currentUser,
|
"currentUser": currentUser,
|
||||||
"domainCount": domainCount,
|
"domainCount": domainCount,
|
||||||
"userCount": userCount,
|
"userCount": userCount,
|
||||||
"totalMails": totalMails,
|
"totalMails": totalMails,
|
||||||
"inboxCount": inboxCount,
|
"inboxCount": inboxCount,
|
||||||
"sentCount": sentCount,
|
"sentCount": sentCount,
|
||||||
"draftsCount": draftsCount,
|
"draftsCount": draftsCount,
|
||||||
"trashCount": trashCount,
|
"trashCount": trashCount,
|
||||||
"totalSize": totalSize,
|
"totalSize": totalSize,
|
||||||
"inboxSize": inboxSize,
|
"inboxSize": inboxSize,
|
||||||
"sentSize": sentSize,
|
"sentSize": sentSize,
|
||||||
"todayReceived": todayReceived,
|
"todayReceived": todayReceived,
|
||||||
"todaySent": todaySent,
|
"todaySent": todaySent,
|
||||||
"weekReceived": weekReceived,
|
"weekReceived": weekReceived,
|
||||||
@@ -202,11 +207,13 @@ func (h *AdminHandler) EditDomain(c *gin.Context) {
|
|||||||
|
|
||||||
currentUser, _ := c.Get("currentUser")
|
currentUser, _ := c.Get("currentUser")
|
||||||
c.HTML(200, "admin_domain_form", gin.H{
|
c.HTML(200, "admin_domain_form", gin.H{
|
||||||
"currentUser": currentUser,
|
"currentUser": currentUser,
|
||||||
"activeFolder": "domains",
|
"activeFolder": "domains",
|
||||||
"error": "",
|
"error": "",
|
||||||
"isEdit": true,
|
"isEdit": true,
|
||||||
"domain": domain,
|
"domain": domain,
|
||||||
|
"tlsPublicCert": readTLSCert(domain.TlsCertPath),
|
||||||
|
"tlsCertConfigured": domain.TlsCertPath != "" && domain.TlsKeyPath != "",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,6 +236,13 @@ func (h *AdminHandler) UpdateDomain(c *gin.Context) {
|
|||||||
domain.Pop3Port = formIntOrDefault(c, "pop3_port", domain.Pop3Port)
|
domain.Pop3Port = formIntOrDefault(c, "pop3_port", domain.Pop3Port)
|
||||||
domain.TlsEnabled = c.PostForm("tls_enabled") == "on"
|
domain.TlsEnabled = c.PostForm("tls_enabled") == "on"
|
||||||
|
|
||||||
|
tlsPrivateKey := strings.TrimSpace(c.PostForm("tls_private_key"))
|
||||||
|
tlsPublicCert := strings.TrimSpace(c.PostForm("tls_public_cert"))
|
||||||
|
if err := h.handleDomainTLSUpdate(domain, tlsPublicCert, tlsPrivateKey); err != nil {
|
||||||
|
h.renderDomainFormError(c, domain, err.Error(), tlsPublicCert)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 重新生成DKIM
|
// 重新生成DKIM
|
||||||
if c.PostForm("regenerate_dkim") == "on" {
|
if c.PostForm("regenerate_dkim") == "on" {
|
||||||
privKey, pubKey, err := dkim.GenerateKeyPair()
|
privKey, pubKey, err := dkim.GenerateKeyPair()
|
||||||
@@ -240,20 +254,84 @@ func (h *AdminHandler) UpdateDomain(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := h.stores.Domains.Update(domain); err != nil {
|
if err := h.stores.Domains.Update(domain); err != nil {
|
||||||
currentUser, _ := c.Get("currentUser")
|
h.renderDomainFormError(c, domain, fmt.Sprintf("更新域名失败: %v", err), tlsPublicCert)
|
||||||
c.HTML(http.StatusBadRequest, "admin_domain_form", gin.H{
|
|
||||||
"currentUser": currentUser,
|
|
||||||
"activeFolder": "domains",
|
|
||||||
"error": fmt.Sprintf("更新域名失败: %v", err),
|
|
||||||
"isEdit": true,
|
|
||||||
"domain": domain,
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Redirect(http.StatusFound, "/admin/domains")
|
c.Redirect(http.StatusFound, "/admin/domains")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readTLSCert(path string) string {
|
||||||
|
if path == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AdminHandler) handleDomainTLSUpdate(domain *db.Domain, publicCert, privateKey string) error {
|
||||||
|
if !domain.TlsEnabled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
hasExistingCert := domain.TlsCertPath != "" && domain.TlsKeyPath != ""
|
||||||
|
if publicCert == "" && privateKey == "" {
|
||||||
|
if hasExistingCert {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("启用 TLS 时必须填写 TLS 私钥和公钥证书")
|
||||||
|
}
|
||||||
|
if hasExistingCert && privateKey == "" && strings.TrimSpace(readTLSCert(domain.TlsCertPath)) == publicCert {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if publicCert == "" || privateKey == "" {
|
||||||
|
return fmt.Errorf("TLS 私钥和公钥证书必须同时填写")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := tls.X509KeyPair([]byte(publicCert), []byte(privateKey)); err != nil {
|
||||||
|
return fmt.Errorf("TLS 证书或私钥无效: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
domainTLSDir := filepath.Join(h.tlsDir, strconv.FormatUint(uint64(domain.ID), 10))
|
||||||
|
if err := os.MkdirAll(domainTLSDir, 0700); err != nil {
|
||||||
|
return fmt.Errorf("创建 TLS 证书目录失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certPath := filepath.Join(domainTLSDir, "cert.pem")
|
||||||
|
keyPath := filepath.Join(domainTLSDir, "key.pem")
|
||||||
|
if err := os.WriteFile(certPath, []byte(publicCert+"\n"), 0644); err != nil {
|
||||||
|
return fmt.Errorf("保存 TLS 公钥证书失败: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(keyPath, []byte(privateKey+"\n"), 0600); err != nil {
|
||||||
|
return fmt.Errorf("保存 TLS 私钥失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
domain.TlsCertPath = certPath
|
||||||
|
domain.TlsKeyPath = keyPath
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AdminHandler) renderDomainFormError(c *gin.Context, domain *db.Domain, message, tlsPublicCert string) {
|
||||||
|
currentUser, _ := c.Get("currentUser")
|
||||||
|
if tlsPublicCert == "" {
|
||||||
|
tlsPublicCert = readTLSCert(domain.TlsCertPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.HTML(http.StatusBadRequest, "admin_domain_form", gin.H{
|
||||||
|
"currentUser": currentUser,
|
||||||
|
"activeFolder": "domains",
|
||||||
|
"error": message,
|
||||||
|
"isEdit": true,
|
||||||
|
"domain": domain,
|
||||||
|
"tlsPublicCert": tlsPublicCert,
|
||||||
|
"tlsCertConfigured": domain.TlsCertPath != "" && domain.TlsKeyPath != "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteDomain removes a domain by ID.
|
// DeleteDomain removes a domain by ID.
|
||||||
func (h *AdminHandler) DeleteDomain(c *gin.Context) {
|
func (h *AdminHandler) DeleteDomain(c *gin.Context) {
|
||||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||||
|
|||||||
+22
-19
@@ -6,6 +6,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"mail_go/config"
|
"mail_go/config"
|
||||||
@@ -35,22 +36,23 @@ func formatBytes(b int64) string {
|
|||||||
|
|
||||||
// WebServer wraps the Gin engine and its dependencies.
|
// WebServer wraps the Gin engine and its dependencies.
|
||||||
type WebServer struct {
|
type WebServer struct {
|
||||||
engine *gin.Engine
|
engine *gin.Engine
|
||||||
stores *store.Stores
|
stores *store.Stores
|
||||||
storage *storage.AttachmentStorage
|
storage *storage.AttachmentStorage
|
||||||
cfg config.WebConfig
|
cfg config.WebConfig
|
||||||
authCfg config.AuthConfig
|
storageCfg config.StorageConfig
|
||||||
banCfg config.BanConfig
|
authCfg config.AuthConfig
|
||||||
|
banCfg config.BanConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// templateFuncs returns custom template functions for rendering.
|
// templateFuncs returns custom template functions for rendering.
|
||||||
func templateFuncs() template.FuncMap {
|
func templateFuncs() template.FuncMap {
|
||||||
return template.FuncMap{
|
return template.FuncMap{
|
||||||
"add": func(a, b int) int { return a + b },
|
"add": func(a, b int) int { return a + b },
|
||||||
"sub": func(a, b int) int { return a - b },
|
"sub": func(a, b int) int { return a - b },
|
||||||
"mul": func(a, b int) int { return a * b },
|
"mul": func(a, b int) int { return a * b },
|
||||||
"div": func(a, b int) int { return a / b },
|
"div": func(a, b int) int { return a / b },
|
||||||
"mod": func(a, b int) int { return a % b },
|
"mod": func(a, b int) int { return a % b },
|
||||||
"ceilDiv": func(a, b int) int { return int(math.Ceil(float64(a) / float64(b))) },
|
"ceilDiv": func(a, b int) int { return int(math.Ceil(float64(a) / float64(b))) },
|
||||||
"seq": func(n int) []int {
|
"seq": func(n int) []int {
|
||||||
result := make([]int, n)
|
result := make([]int, n)
|
||||||
@@ -76,7 +78,7 @@ func templateFuncs() template.FuncMap {
|
|||||||
|
|
||||||
// NewWebServer creates a new WebServer, initializes the Gin engine,
|
// NewWebServer creates a new WebServer, initializes the Gin engine,
|
||||||
// configures sessions, middleware, and registers all routes.
|
// configures sessions, middleware, and registers all routes.
|
||||||
func NewWebServer(cfg config.WebConfig, stores *store.Stores, attStorage *storage.AttachmentStorage, authCfg config.AuthConfig, banCfg config.BanConfig) *WebServer {
|
func NewWebServer(cfg config.WebConfig, stores *store.Stores, attStorage *storage.AttachmentStorage, storageCfg config.StorageConfig, authCfg config.AuthConfig, banCfg config.BanConfig) *WebServer {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
engine := gin.New()
|
engine := gin.New()
|
||||||
engine.Use(gin.Logger())
|
engine.Use(gin.Logger())
|
||||||
@@ -99,12 +101,13 @@ func NewWebServer(cfg config.WebConfig, stores *store.Stores, attStorage *storag
|
|||||||
engine.SetHTMLTemplate(tmpl)
|
engine.SetHTMLTemplate(tmpl)
|
||||||
|
|
||||||
ws := &WebServer{
|
ws := &WebServer{
|
||||||
engine: engine,
|
engine: engine,
|
||||||
stores: stores,
|
stores: stores,
|
||||||
storage: attStorage,
|
storage: attStorage,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
authCfg: authCfg,
|
storageCfg: storageCfg,
|
||||||
banCfg: banCfg,
|
authCfg: authCfg,
|
||||||
|
banCfg: banCfg,
|
||||||
}
|
}
|
||||||
|
|
||||||
ws.registerRoutes()
|
ws.registerRoutes()
|
||||||
@@ -115,7 +118,7 @@ func NewWebServer(cfg config.WebConfig, stores *store.Stores, attStorage *storag
|
|||||||
func (ws *WebServer) registerRoutes() {
|
func (ws *WebServer) registerRoutes() {
|
||||||
authHandler := handlers.NewAuthHandler(ws.stores, ws.authCfg, ws.banCfg)
|
authHandler := handlers.NewAuthHandler(ws.stores, ws.authCfg, ws.banCfg)
|
||||||
mailHandler := handlers.NewMailHandler(ws.stores, ws.storage)
|
mailHandler := handlers.NewMailHandler(ws.stores, ws.storage)
|
||||||
adminHandler := handlers.NewAdminHandler(ws.stores, ws.storage)
|
adminHandler := handlers.NewAdminHandler(ws.stores, ws.storage, filepath.Join(ws.storageCfg.BaseDir, "tls", "domains"))
|
||||||
|
|
||||||
// Apply BanMiddleware globally before public routes
|
// Apply BanMiddleware globally before public routes
|
||||||
ws.engine.Use(middleware.BanMiddleware(ws.stores))
|
ws.engine.Use(middleware.BanMiddleware(ws.stores))
|
||||||
|
|||||||
@@ -51,13 +51,36 @@
|
|||||||
</label>
|
</label>
|
||||||
<p style="color:#7f8c8d;font-size:12px;margin-top:4px;">勾选后端口将自动切换为 SSL/TLS 标准端口</p>
|
<p style="color:#7f8c8d;font-size:12px;margin-top:4px;">勾选后端口将自动切换为 SSL/TLS 标准端口</p>
|
||||||
</div>
|
</div>
|
||||||
|
{{if .isEdit}}
|
||||||
|
<div id="tls_cert_fields" style="display:none;">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>TLS 私钥 PEM</label>
|
||||||
|
<textarea name="tls_private_key" rows="8" placeholder="-----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY-----" style="font-family:monospace;"></textarea>
|
||||||
|
<p style="color:#7f8c8d;font-size:12px;margin-top:4px;">私钥不会回显;留空则保留现有私钥。</p>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>TLS 公钥证书 PEM</label>
|
||||||
|
<textarea name="tls_public_cert" rows="8" placeholder="-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----" style="font-family:monospace;">{{.tlsPublicCert}}</textarea>
|
||||||
|
{{if .tlsCertConfigured}}
|
||||||
|
<p style="color:#27ae60;font-size:12px;margin-top:4px;">✅ TLS 证书已配置;上传新证书后需重启服务生效。</p>
|
||||||
|
{{else}}
|
||||||
|
<p style="color:#e67e22;font-size:12px;margin-top:4px;">⚠️ TLS 证书未配置,启用 TLS 时必须同时填写私钥和证书。</p>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
<script>
|
<script>
|
||||||
function togglePorts() {
|
function togglePorts() {
|
||||||
var tls = document.getElementById('tls_enabled').checked;
|
var tls = document.getElementById('tls_enabled').checked;
|
||||||
document.querySelector('input[name="smtp_port"]').value = tls ? 465 : 25;
|
document.querySelector('input[name="smtp_port"]').value = tls ? 465 : 25;
|
||||||
document.querySelector('input[name="imap_port"]').value = tls ? 993 : 143;
|
document.querySelector('input[name="imap_port"]').value = tls ? 993 : 143;
|
||||||
document.querySelector('input[name="pop3_port"]').value = tls ? 995 : 110;
|
document.querySelector('input[name="pop3_port"]').value = tls ? 995 : 110;
|
||||||
|
var certFields = document.getElementById('tls_cert_fields');
|
||||||
|
if (certFields) {
|
||||||
|
certFields.style.display = tls ? 'block' : 'none';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
document.addEventListener('DOMContentLoaded', togglePorts);
|
||||||
</script>
|
</script>
|
||||||
{{if .isEdit}}
|
{{if .isEdit}}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
@@ -16,6 +16,34 @@ import (
|
|||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func applyDomainTLSConfig(stores *store.Stores, cfg *config.Config) {
|
||||||
|
domain, err := stores.Domains.GetFirstTLSEnabledWithCert()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
applied := false
|
||||||
|
if cfg.SMTP.TLSCert == "" && cfg.SMTP.TLSKey == "" {
|
||||||
|
cfg.SMTP.TLSCert = domain.TlsCertPath
|
||||||
|
cfg.SMTP.TLSKey = domain.TlsKeyPath
|
||||||
|
applied = true
|
||||||
|
}
|
||||||
|
if cfg.IMAP.TLSCert == "" && cfg.IMAP.TLSKey == "" {
|
||||||
|
cfg.IMAP.TLSCert = domain.TlsCertPath
|
||||||
|
cfg.IMAP.TLSKey = domain.TlsKeyPath
|
||||||
|
applied = true
|
||||||
|
}
|
||||||
|
if cfg.POP3.TLSCert == "" && cfg.POP3.TLSKey == "" {
|
||||||
|
cfg.POP3.TLSCert = domain.TlsCertPath
|
||||||
|
cfg.POP3.TLSKey = domain.TlsKeyPath
|
||||||
|
applied = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if applied {
|
||||||
|
log.Printf("使用域名 %s 的 TLS 证书;更新证书后需重启服务生效", domain.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// 1. Load configuration
|
// 1. Load configuration
|
||||||
cfg, err := config.LoadConfig()
|
cfg, err := config.LoadConfig()
|
||||||
@@ -39,6 +67,7 @@ func main() {
|
|||||||
|
|
||||||
// 5. Initialize attachment storage
|
// 5. Initialize attachment storage
|
||||||
attStorage := storage.NewAttachmentStorage(cfg.Storage.AttachDir)
|
attStorage := storage.NewAttachmentStorage(cfg.Storage.AttachDir)
|
||||||
|
applyDomainTLSConfig(stores, cfg)
|
||||||
|
|
||||||
// 6. Start SMTP server
|
// 6. Start SMTP server
|
||||||
smtpSrv := smtp_server.NewSMTPServer(cfg.SMTP, stores, attStorage)
|
smtpSrv := smtp_server.NewSMTPServer(cfg.SMTP, stores, attStorage)
|
||||||
@@ -89,7 +118,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 9. Start Web server
|
// 9. Start Web server
|
||||||
webServer := web.NewWebServer(cfg.Web, stores, attStorage, cfg.Auth, cfg.Ban)
|
webServer := web.NewWebServer(cfg.Web, stores, attStorage, cfg.Storage, cfg.Auth, cfg.Ban)
|
||||||
fmt.Printf("Web 服务启动在 %s\n", cfg.Web.Addr)
|
fmt.Printf("Web 服务启动在 %s\n", cfg.Web.Addr)
|
||||||
go func() {
|
go func() {
|
||||||
if err := webServer.Start(); err != nil {
|
if err := webServer.Start(); err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user