diff --git a/apps/api/internal/app/mime.go b/apps/api/internal/app/mime.go index 59a6962..a479579 100644 --- a/apps/api/internal/app/mime.go +++ b/apps/api/internal/app/mime.go @@ -16,6 +16,8 @@ import ( "time" ) +const smtpSessionTimeout = 45 * time.Second + type MIMEMessage struct { From string To []string @@ -142,10 +144,11 @@ func sendSMTPWithConfig(cfg Config, from string, recipients []string, mimeBytes } func sendSMTPPlain(addr, host string, auth smtp.Auth, from string, recipients []string, mimeBytes []byte) error { - conn, err := net.Dial("tcp", addr) + conn, err := net.DialTimeout("tcp", addr, 10*time.Second) if err != nil { return err } + _ = conn.SetDeadline(time.Now().Add(smtpSessionTimeout)) client, err := smtp.NewClient(conn, host) if err != nil { _ = conn.Close() @@ -156,10 +159,12 @@ func sendSMTPPlain(addr, host string, auth smtp.Auth, from string, recipients [] } func sendSMTPImplicitTLS(addr, host string, auth smtp.Auth, from string, recipients []string, mimeBytes []byte) error { - conn, err := tls.Dial("tcp", addr, &tls.Config{ServerName: host, MinVersion: tls.VersionTLS12}) + dialer := &net.Dialer{Timeout: 10 * time.Second} + conn, err := tls.DialWithDialer(dialer, "tcp", addr, &tls.Config{ServerName: host, MinVersion: tls.VersionTLS12}) if err != nil { return err } + _ = conn.SetDeadline(time.Now().Add(smtpSessionTimeout)) client, err := smtp.NewClient(conn, host) if err != nil { _ = conn.Close() @@ -170,10 +175,11 @@ func sendSMTPImplicitTLS(addr, host string, auth smtp.Auth, from string, recipie } func sendSMTPStartTLS(addr, host string, auth smtp.Auth, from string, recipients []string, mimeBytes []byte) error { - conn, err := net.Dial("tcp", addr) + conn, err := net.DialTimeout("tcp", addr, 10*time.Second) if err != nil { return err } + _ = conn.SetDeadline(time.Now().Add(smtpSessionTimeout)) client, err := smtp.NewClient(conn, host) if err != nil { _ = conn.Close() diff --git a/apps/web/src/lib/api.ts b/apps/web/src/lib/api.ts index 1fb5712..fca90ad 100644 --- a/apps/web/src/lib/api.ts +++ b/apps/web/src/lib/api.ts @@ -2,17 +2,19 @@ import type { User, AdminUser, AdminOverview, Domain, Mailbox, Alias, MailFolder export * from "./api-types" const REQUEST_TIMEOUT_MS = 15_000 +const MAIL_DELIVERY_TIMEOUT_MS = 60_000 -async function request(path: string, init: RequestInit = {}): Promise { +async function request(path: string, init: RequestInit & { timeoutMs?: number } = {}): Promise { + const { timeoutMs, ...requestInit } = init const controller = new AbortController() - const timeout = window.setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS) - const externalSignal = init.signal + const timeout = window.setTimeout(() => controller.abort(), timeoutMs || REQUEST_TIMEOUT_MS) + const externalSignal = requestInit.signal if (externalSignal) { if (externalSignal.aborted) controller.abort() else externalSignal.addEventListener("abort", () => controller.abort(), { once: true }) } try { - const res = await fetch(path, { credentials: "include", headers: { "Content-Type": "application/json", ...(init.headers || {}) }, ...init, signal: controller.signal }) + const res = await fetch(path, { credentials: "include", headers: { "Content-Type": "application/json", ...(requestInit.headers || {}) }, ...requestInit, signal: controller.signal }) if (!res.ok) { let message = `${res.status} ${res.statusText}` try { const body = await res.json(); message = body.error || message } catch {} @@ -83,7 +85,7 @@ export const api = { adminMessage: (id: string) => request(`/api/admin/messages/${id}`), systemSettings: () => request("/api/admin/settings"), updateSystemSettings: (payload: SystemSettingsPayload) => request("/api/admin/settings", { method: "POST", body: JSON.stringify(payload) }), - testSmtp: (to: string) => request<{ ok: boolean }>("/api/admin/settings/test-smtp", { method: "POST", body: JSON.stringify({ to }) }), + testSmtp: (to: string) => request<{ ok: boolean }>("/api/admin/settings/test-smtp", { method: "POST", body: JSON.stringify({ to }), timeoutMs: MAIL_DELIVERY_TIMEOUT_MS }), mailTemplates: () => request>("/api/admin/mail-templates"), updateMailTemplate: (key: string, payload: { subject: string; bodyText: string; bodyHtml: string }) => request(`/api/admin/mail-templates/${encodeURIComponent(key)}`, { method: "POST", body: JSON.stringify(payload) }), resetMailTemplate: (key: string) => request(`/api/admin/mail-templates/${encodeURIComponent(key)}/reset`, { method: "POST" }), @@ -112,7 +114,7 @@ export const api = { return request>(`/api/mail/starred?${params.toString()}`) }, message: (id: string, options: { markRead?: boolean } = {}) => request(`/api/mail/messages/${id}${options.markRead === false ? "?markRead=0" : ""}`), - send: (payload: SendPayload) => request("/api/mail/send", { method: "POST", body: JSON.stringify(payload) }), + send: (payload: SendPayload) => request("/api/mail/send", { method: "POST", body: JSON.stringify(payload), timeoutMs: MAIL_DELIVERY_TIMEOUT_MS }), markRead: (id: string, read: boolean) => request<{ ok: boolean }>(`/api/mail/messages/${id}/mark-read`, { method: "POST", body: JSON.stringify({ read }) }), star: (id: string, starred: boolean) => request<{ ok: boolean }>(`/api/mail/messages/${id}/star`, { method: "POST", body: JSON.stringify({ starred }) }), addLabel: (id: string, payload: { name: string; color?: string }) => request<{ labels: MailLabel[] }>(`/api/mail/messages/${id}/labels`, { method: "POST", body: JSON.stringify(payload) }), diff --git a/deploy/postfix/master.cf b/deploy/postfix/master.cf index 9ed8fd4..b72df95 100644 --- a/deploy/postfix/master.cf +++ b/deploy/postfix/master.cf @@ -4,26 +4,26 @@ submission inet n - n - - smtpd -o smtpd_tls_security_level=may -o smtpd_sasl_auth_enable=yes -o smtpd_relay_restrictions=permit_sasl_authenticated,reject -pickup unix n - y 60 1 pickup -cleanup unix n - y - 0 cleanup +pickup unix n - n 60 1 pickup +cleanup unix n - n - 0 cleanup qmgr unix n - n 300 1 qmgr -tlsmgr unix - - y 1000? 1 tlsmgr -rewrite unix - - y - - trivial-rewrite -bounce unix - - y - 0 bounce -defer unix - - y - 0 bounce -trace unix - - y - 0 bounce -verify unix - - y - 1 verify +tlsmgr unix - - n 1000? 1 tlsmgr +rewrite unix - - n - - trivial-rewrite +bounce unix - - n - 0 bounce +defer unix - - n - 0 bounce +trace unix - - n - 0 bounce +verify unix - - n - 1 verify flush unix n - y 1000? 0 flush proxymap unix - - n - - proxymap proxywrite unix - - n - 1 proxymap -smtp unix - - y - - smtp -relay unix - - y - - smtp -showq unix n - y - - showq -error unix - - y - - error -retry unix - - y - - error -discard unix - - y - - discard +smtp unix - - n - - smtp +relay unix - - n - - smtp +showq unix n - n - - showq +error unix - - n - - error +retry unix - - n - - error +discard unix - - n - - discard local unix - n n - - local virtual unix - n n - - virtual -lmtp unix - - y - - lmtp -anvil unix - - y - 1 anvil -scache unix - - y - 1 scache +lmtp unix - - n - - lmtp +anvil unix - - n - 1 anvil +scache unix - - n - 1 scache diff --git a/deploy/rspamd/local.d/worker-proxy.inc b/deploy/rspamd/local.d/worker-proxy.inc index 4ecc3e4..9fb92bc 100644 --- a/deploy/rspamd/local.d/worker-proxy.inc +++ b/deploy/rspamd/local.d/worker-proxy.inc @@ -1,6 +1,6 @@ bind_socket = "0.0.0.0:11332"; milter = yes; -timeout = 120s; +timeout = 10s; upstream "local" { default = yes;