-
Notifications
You must be signed in to change notification settings - Fork 38
/
lib_smtp.lua
193 lines (176 loc) · 7.38 KB
/
lib_smtp.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
local sys = require("sys")
-- local zbuff = require("zbuff")
local lib_smtp = {}
lib_smtp.socket_debug_enable = false
lib_smtp.packet_size = 512
lib_smtp.timeout = 1000 * 30
--- 日志格式化函数
-- @param content string, 日志内容
-- @return string, 处理后的日志内容
local function logFormat(content)
-- 隐藏 AUTH 用户信息
content = content:gsub("AUTH PLAIN (.-)\r\n", "AUTH PLAIN ***\r\n")
-- 替换换行符
content = content:gsub("\r", "\\r"):gsub("\n", "\\n")
-- 截取
content = content:sub(1, 200) .. (#content > 200 and " ..." or "")
return content
end
--- 转义句号函数
-- @param content string, 需要转义的内容
-- @return string, 转义后的内容
local function escapeDot(content)
return content:gsub("(.-\r\n)", function(line)
if line:sub(1, 1) == "." then line = "." .. line end
return line
end)
end
--- 接收到数据时的处理函数
-- @param netc userdata, socket.create 返回的 netc
-- @param rxbuf userdata, 接收到的数据
-- @param socket_id string, socket id
-- @param current_command string, 当前要发送的命令
local function recvHandler(netc, rxbuf, socket_id, current_command)
local rx_str = rxbuf:toStr(0, rxbuf:used())
log.info("lib_smtp", socket_id, "<-", logFormat(rx_str))
-- 如果返回非 2xx 或 3xx 状态码, 则断开连接
if not rx_str:match("^[23]%d%d") then
log.error("lib_smtp", socket_id, "服务器返回错误状态码, 断开连接, 请检查日志")
sys.publish(socket_id .. "_disconnect", { success = false, message = "服务器返回错误状态码", is_retry = false })
return
end
if current_command == nil then
log.info("lib_smtp", socket_id, "全部发送完成")
sys.publish(socket_id .. "_disconnect", { success = true, message = "发送成功", is_retry = false })
return
end
-- 分包发送
local index = 1
sys.taskInit(function()
while index <= #current_command do
local packet = current_command:sub(index, index + lib_smtp.packet_size - 1)
socket.tx(netc, packet)
log.info("lib_smtp", socket_id, "->", logFormat(packet))
index = index + lib_smtp.packet_size
sys.wait(100)
end
end)
end
local function validateParameters(smtp_config)
-- 配置参数验证规则
local validation_rules = {
{ field = "host", type = "string", required = true },
{ field = "port", type = "number", required = true },
{ field = "username", type = "string", required = true },
{ field = "password", type = "string", required = true },
{ field = "mail_from", type = "string", required = true },
{ field = "mail_to", type = "string", required = true },
{ field = "tls_enable", type = "boolean", required = false },
}
local result = true
for _, rule in ipairs(validation_rules) do
local value = smtp_config[rule.field]
if rule.type == "string" and (value == nil or value == "") then
log.error("lib_smtp", string.format("`smtp_config.%s` 应为非空字符串", rule.field))
result = false
elseif rule.required and type(value) ~= rule.type then
log.error("lib_smtp", string.format("`smtp_config.%s` 应为 %s 类型", rule.field, rule.type))
result = false
end
end
return result
end
--- 发送邮件
-- @param body string 邮件正文
-- @param subject string 邮件主题
-- @param smtp_config table 配置参数
-- - smtp_config.host string SMTP 服务器地址
-- - smtp_config.username string SMTP 账号用户名
-- - smtp_config.password string SMTP 账号密码
-- - smtp_config.mail_from string 发件人邮箱地址
-- - smtp_config.mail_to string 收件人邮箱地址
-- - smtp_config.port number SMTP 服务器端口号
-- - smtp_config.tls_enable boolean 是否启用 TLS(可选,默认为 false)
-- @return result table 发送结果
-- - result.success boolean 是否发送成功
-- - result.message string 发送结果描述
-- - result.is_retry boolean 是否需要重试
function lib_smtp.send(body, sender_number, smtp_config)
-- 参数验证
if type(smtp_config) ~= "table" then
log.error("lib_smtp", "`smtp_config` 应为 table 类型")
return { success = false, message = "参数错误", is_retry = false }
end
local valid = validateParameters(smtp_config)
if not valid then return { success = false, message = "参数错误", is_retry = false } end
subject = type(sender_number) == "string" and string.format("%s \r\n %s",smtp_config.subject_config,sender_number) or ""
body = type(body) == "string" and escapeDot(body) or ""
lib_smtp.send_count = (lib_smtp.send_count or 0) + 1
local socket_id = "socket_" .. lib_smtp.send_count
local rxbuf = zbuff.create(256)
local commands = {
"HELO " .. smtp_config.host .. "\r\n",
"AUTH PLAIN " .. string.toBase64("\0" .. smtp_config.username .. "\0" .. smtp_config.password) .. "\r\n",
"MAIL FROM: <" .. smtp_config.mail_from .. ">\r\n",
"RCPT TO: <" .. smtp_config.mail_to .. ">\r\n",
"DATA\r\n",
table.concat({
"From: " .. smtp_config.mail_from,
"To: " .. smtp_config.mail_to,
"Subject: " .. subject,
"Content-Type: text/plain; charset=UTF-8",
"",
body,
".",
"",
}, "\r\n"),
}
local current_command_index = 1
local function getNextCommand()
local command = commands[current_command_index]
current_command_index = current_command_index + 1
return command
end
-- socket 回调
local function netCB(netc, event, param)
if param ~= 0 then
sys.publish(socket_id .. "_disconnect", { success = false, message = "param~=0", is_retry = true })
return
end
if event == socket.LINK then
log.info("lib_smtp", socket_id, "LINK")
elseif event == socket.ON_LINE then
log.info("lib_smtp", socket_id, "ON_LINE")
elseif event == socket.EVENT then
socket.rx(netc, rxbuf)
socket.wait(netc)
if rxbuf:used() > 0 then recvHandler(netc, rxbuf, socket_id, getNextCommand()) end
rxbuf:del()
elseif event == socket.TX_OK then
socket.wait(netc)
elseif event == socket.CLOSE then
log.info("lib_smtp", socket_id, "CLOSED")
sys.publish(socket_id .. "_disconnect", { success = false, message = "服务器断开连接", is_retry = true })
end
end
-- 初始化 socket
local netc = socket.create(nil, netCB)
socket.debug(netc, lib_smtp.socket_debug_enable)
socket.config(netc, nil, nil, smtp_config.tls_enable)
-- 连接 smtp 服务器
local is_connect_success = socket.connect(netc, smtp_config.host, smtp_config.port)
if not is_connect_success then
socket.close(netc)
return { success = false, message = "未知错误", is_retry = true }
end
-- 等待发送结果
local is_send_success, send_result = sys.waitUntil(socket_id .. "_disconnect", lib_smtp.timeout)
socket.close(netc)
if is_send_success then
return send_result
else
log.error("lib_smtp", socket_id, "发送超时")
return { success = false, message = "发送超时", is_retry = true }
end
end
return lib_smtp