From ddfa2c5d03b06ca5ddd2a9e1fe6e39e76d191e43 Mon Sep 17 00:00:00 2001 From: Dream Hunter Date: Thu, 7 Nov 2024 00:58:15 +0800 Subject: [PATCH] feat: add ENABLE_CHECK_JUNK_MAIL (#469) --- CHANGELOG.md | 1 + vitepress-docs/docs/en/cli.md | 2 + vitepress-docs/docs/zh/guide/cli/worker.md | 2 + worker/src/admin_api/worker_config.ts | 3 +- worker/src/common.ts | 4 +- worker/src/email/check_junk.ts | 44 ++++++++++++++++++++++ worker/src/email/index.ts | 14 +++++++ worker/src/types.d.ts | 2 + worker/wrangler.toml.template | 2 + 9 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 worker/src/email/check_junk.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index effb5d4c..4a5e954d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - feat: 支持提前设置 bot info, 降低 telegram 回调延迟 (#441) - feat: 增加 telegram mini app 的 build 压缩包 +- feat: 增加是否启用垃圾邮件检查 `ENABLE_CHECK_JUNK_MAIL` 配置 ## v0.7.5 diff --git a/vitepress-docs/docs/en/cli.md b/vitepress-docs/docs/en/cli.md index 83ba0811..9aeba05f 100644 --- a/vitepress-docs/docs/en/cli.md +++ b/vitepress-docs/docs/en/cli.md @@ -130,6 +130,8 @@ ENABLE_AUTO_REPLY = false # FORWARD_ADDRESS_LIST = ["xxx@xxx.com"] # Frontend URL # FRONTEND_URL = "https://xxxx.xxx" +# Enable check junk mail +# ENABLE_CHECK_JUNK_MAIL = false [[d1_databases]] binding = "DB" diff --git a/vitepress-docs/docs/zh/guide/cli/worker.md b/vitepress-docs/docs/zh/guide/cli/worker.md index 92228912..3b697f7b 100644 --- a/vitepress-docs/docs/zh/guide/cli/worker.md +++ b/vitepress-docs/docs/zh/guide/cli/worker.md @@ -103,6 +103,8 @@ ENABLE_AUTO_REPLY = false # FORWARD_ADDRESS_LIST = ["xxx@xxx.com"] # 前端地址,用于发送 webhook 的邮件 url # FRONTEND_URL = "https://xxxx.xxx" +# 是否启用垃圾邮件检查 +# ENABLE_CHECK_JUNK_MAIL = false # D1 数据库的名称和 ID 可以在 cloudflare 控制台查看 [[d1_databases]] diff --git a/worker/src/admin_api/worker_config.ts b/worker/src/admin_api/worker_config.ts index f1400f89..625b5739 100644 --- a/worker/src/admin_api/worker_config.ts +++ b/worker/src/admin_api/worker_config.ts @@ -40,7 +40,8 @@ export default { "S3_ENABLED": isS3Enabled(c), "VERSION": CONSTANTS.VERSION, "DISABLE_SHOW_GITHUB": !getBooleanValue(c.env.DISABLE_SHOW_GITHUB), - "DISABLE_ADMIN_PASSWORD_CHECK": getBooleanValue(c.env.DISABLE_ADMIN_PASSWORD_CHECK) + "DISABLE_ADMIN_PASSWORD_CHECK": getBooleanValue(c.env.DISABLE_ADMIN_PASSWORD_CHECK), + "ENABLE_CHECK_JUNK_MAIL": getBooleanValue(c.env.ENABLE_CHECK_JUNK_MAIL), }) } } diff --git a/worker/src/common.ts b/worker/src/common.ts index 5685d979..0f488990 100644 --- a/worker/src/common.ts +++ b/worker/src/common.ts @@ -260,7 +260,8 @@ export const commonParseMail = async (raw_mail: string | undefined | null): Prom sender: string, subject: string, text: string, - html: string + html: string, + headers?: Record[] } | undefined> => { if (!raw_mail) { return undefined; @@ -287,6 +288,7 @@ export const commonParseMail = async (raw_mail: string | undefined | null): Prom subject: parsedEmail.subject || "", text: parsedEmail.text || "", html: parsedEmail.html || "", + headers: parsedEmail.headers || [], }; } catch (e) { diff --git a/worker/src/email/check_junk.ts b/worker/src/email/check_junk.ts new file mode 100644 index 00000000..54e527e0 --- /dev/null +++ b/worker/src/email/check_junk.ts @@ -0,0 +1,44 @@ +import { Bindings } from "../types"; +import { getBooleanValue } from "../utils"; +import { commonParseMail } from "../common"; + +export const check_if_junk_mail = async ( + env: Bindings, address: string, + raw_mail: string, message_id: string | null +): Promise => { + if (!getBooleanValue(env.ENABLE_CHECK_JUNK_MAIL)) { + return false; + } + const parsedEmail = await commonParseMail(raw_mail); + if (!parsedEmail?.headers) return false; + const headers = parsedEmail.headers; + for (const header of headers) { + if (!header["key"]) continue; + if (!header["value"]) continue; + + // check spf + if (header["key"].toLowerCase() == "received-spf" + && + !header["value"].toLowerCase().includes("pass") + ) { + return true; + } + + // check dkim and dmarc + if (header["key"].toLowerCase() == "authentication-results") { + if (header["value"].toLowerCase().includes("dkim=") + && + !header["value"].toLowerCase().includes("dkim=pass") + ) { + return true; + } + if (header["value"].toLowerCase().includes("dmarc=") + && + !header["value"].toLowerCase().includes("dmarc=pass") + ) { + return true; + } + } + } + return false; +} diff --git a/worker/src/email/index.ts b/worker/src/email/index.ts index 44ac7c63..3ff88396 100644 --- a/worker/src/email/index.ts +++ b/worker/src/email/index.ts @@ -6,6 +6,7 @@ import { Bindings, HonoCustomType } from "../types"; import { auto_reply } from "./auto_reply"; import { isBlocked } from "./black_list"; import { triggerWebhook } from "../common"; +import { check_if_junk_mail } from "./check_junk"; async function email(message: ForwardableEmailMessage, env: Bindings, ctx: ExecutionContext) { @@ -15,6 +16,19 @@ async function email(message: ForwardableEmailMessage, env: Bindings, ctx: Execu return; } const rawEmail = await new Response(message.raw).text(); + + // check if junk mail + try { + const is_junk = await check_if_junk_mail(env, message.to, rawEmail, message.headers.get("Message-ID")); + if (is_junk) { + message.setReject("Junk mail"); + console.log(`Junk mail from ${message.from} to ${message.to}`); + return; + } + } catch (error) { + console.log("check junk mail error", error); + } + const message_id = message.headers.get("Message-ID"); // save email const { success } = await env.DB.prepare( diff --git a/worker/src/types.d.ts b/worker/src/types.d.ts index 8775601a..fc8ea2b7 100644 --- a/worker/src/types.d.ts +++ b/worker/src/types.d.ts @@ -42,6 +42,8 @@ export type Bindings = { DISABLE_SHOW_GITHUB: string | boolean | undefined FORWARD_ADDRESS_LIST: string | string[] | undefined + ENABLE_CHECK_JUNK_MAIL: string | boolean | undefined + // s3 config S3_ENDPOINT: string | undefined S3_ACCESS_KEY_ID: string | undefined diff --git a/worker/wrangler.toml.template b/worker/wrangler.toml.template index 71b93fbd..c0e25297 100644 --- a/worker/wrangler.toml.template +++ b/worker/wrangler.toml.template @@ -72,6 +72,8 @@ ENABLE_AUTO_REPLY = false # FORWARD_ADDRESS_LIST = ["xxx@xxx.com"] # Frontend URL # FRONTEND_URL = "https://xxxx.xxx" +# Enable check junk mail +# ENABLE_CHECK_JUNK_MAIL = false [[d1_databases]] binding = "DB"