2019-05-25 15:12:44 +02:00
|
|
|
--[[
|
2022-03-25 21:16:35 +01:00
|
|
|
Copyright (c) 2022, Vsevolod Stakhov <vsevolod@rspamd.com>
|
2019-05-25 15:12:44 +02:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
]]--
|
|
|
|
|
|
|
|
local rspamd_tcp = require "rspamd_tcp"
|
2019-05-25 15:24:39 +02:00
|
|
|
local lua_util = require "lua_util"
|
2019-05-25 15:12:44 +02:00
|
|
|
|
|
|
|
local exports = {}
|
|
|
|
|
|
|
|
local CRLF = '\r\n'
|
2019-05-25 15:21:25 +02:00
|
|
|
local default_timeout = 10.0
|
2019-05-25 15:12:44 +02:00
|
|
|
|
|
|
|
--[[[
|
|
|
|
-- @function lua_smtp.sendmail(task, message, opts, callback)
|
|
|
|
--]]
|
2019-05-25 15:24:39 +02:00
|
|
|
local function sendmail(opts, message, callback)
|
2019-05-25 15:12:44 +02:00
|
|
|
local stage = 'connect'
|
|
|
|
|
|
|
|
local function mail_cb(err, data, conn)
|
|
|
|
local function no_error_write(merr)
|
|
|
|
if merr then
|
|
|
|
callback(false, string.format('error on stage %s: %s',
|
|
|
|
stage, merr))
|
|
|
|
if conn then
|
|
|
|
conn:close()
|
|
|
|
end
|
|
|
|
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
local function no_error_read(merr, mdata, wantcode)
|
|
|
|
wantcode = wantcode or '2'
|
|
|
|
if merr then
|
|
|
|
callback(false, string.format('error on stage %s: %s',
|
|
|
|
stage, merr))
|
|
|
|
if conn then
|
|
|
|
conn:close()
|
|
|
|
end
|
|
|
|
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
if mdata then
|
|
|
|
if type(mdata) ~= 'string' then
|
|
|
|
mdata = tostring(mdata)
|
|
|
|
end
|
|
|
|
if string.sub(mdata, 1, 1) ~= wantcode then
|
2021-11-21 17:33:41 +01:00
|
|
|
callback(false, string.format('bad smtp response on stage %s: "%s" when "%s" expected',
|
2019-05-25 15:12:44 +02:00
|
|
|
stage, mdata, wantcode))
|
|
|
|
if conn then
|
|
|
|
conn:close()
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
else
|
|
|
|
callback(false, string.format('no data on stage %s',
|
|
|
|
stage))
|
|
|
|
if conn then
|
|
|
|
conn:close()
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
-- After quit
|
|
|
|
local function all_done_cb(merr, mdata)
|
|
|
|
if conn then
|
|
|
|
conn:close()
|
|
|
|
end
|
|
|
|
|
|
|
|
callback(true, nil)
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
-- QUIT stage
|
|
|
|
local function quit_done_cb(_, _)
|
|
|
|
conn:add_read(all_done_cb, CRLF)
|
|
|
|
end
|
|
|
|
local function quit_cb(merr, mdata)
|
|
|
|
if no_error_read(merr, mdata) then
|
|
|
|
conn:add_write(quit_done_cb, 'QUIT' .. CRLF)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
local function pre_quit_cb(merr, _)
|
|
|
|
if no_error_write(merr) then
|
|
|
|
stage = 'quit'
|
|
|
|
conn:add_read(quit_cb, CRLF)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- DATA stage
|
|
|
|
local function data_done_cb(merr, mdata)
|
|
|
|
if no_error_read(merr, mdata, '3') then
|
2019-11-04 14:08:07 +01:00
|
|
|
if type(message) == 'string' or type(message) == 'userdata' then
|
2019-05-25 15:44:06 +02:00
|
|
|
conn:add_write(pre_quit_cb, {message, CRLF.. '.' .. CRLF})
|
|
|
|
else
|
|
|
|
table.insert(message, CRLF.. '.' .. CRLF)
|
|
|
|
conn:add_write(pre_quit_cb, message)
|
|
|
|
end
|
2019-05-25 15:12:44 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
local function data_cb(merr, _)
|
|
|
|
if no_error_write(merr) then
|
|
|
|
conn:add_read(data_done_cb, CRLF)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- RCPT phase
|
|
|
|
local next_recipient
|
|
|
|
local function rcpt_done_cb_gen(i)
|
|
|
|
return function (merr, mdata)
|
|
|
|
if no_error_read(merr, mdata) then
|
|
|
|
if i == #opts.recipients then
|
|
|
|
conn:add_write(data_cb, 'DATA' .. CRLF)
|
|
|
|
else
|
|
|
|
next_recipient(i + 1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function rcpt_cb_gen(i)
|
|
|
|
return function (merr, _)
|
|
|
|
if no_error_write(merr, '2') then
|
|
|
|
conn:add_read(rcpt_done_cb_gen(i), CRLF)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
next_recipient = function(i)
|
|
|
|
conn:add_write(rcpt_cb_gen(i),
|
|
|
|
string.format('RCPT TO: <%s>%s', opts.recipients[i], CRLF))
|
|
|
|
end
|
|
|
|
|
|
|
|
-- FROM stage
|
|
|
|
local function from_done_cb(merr, mdata)
|
|
|
|
-- We need to iterate over recipients sequentially
|
|
|
|
if no_error_read(merr, mdata, '2') then
|
|
|
|
stage = 'rcpt'
|
|
|
|
next_recipient(1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
local function from_cb(merr, _)
|
|
|
|
if no_error_write(merr) then
|
|
|
|
conn:add_read(from_done_cb, CRLF)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
local function hello_done_cb(merr, mdata)
|
|
|
|
if no_error_read(merr, mdata) then
|
|
|
|
stage = 'from'
|
|
|
|
conn:add_write(from_cb, string.format(
|
|
|
|
'MAIL FROM: <%s>%s', opts.from, CRLF))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-11-21 17:33:41 +01:00
|
|
|
-- HELO stage
|
2019-05-25 15:12:44 +02:00
|
|
|
local function hello_cb(merr)
|
|
|
|
if no_error_write(merr) then
|
|
|
|
conn:add_read(hello_done_cb, CRLF)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if no_error_read(err, data) then
|
|
|
|
stage = 'helo'
|
|
|
|
conn:add_write(hello_cb, string.format('HELO %s%s',
|
|
|
|
opts.helo, CRLF))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if type(opts.recipients) == 'string' then
|
|
|
|
opts.recipients = {opts.recipients}
|
|
|
|
end
|
|
|
|
|
2019-05-25 15:24:39 +02:00
|
|
|
local tcp_opts = lua_util.shallowcopy(opts)
|
|
|
|
tcp_opts.stop_pattern = CRLF
|
|
|
|
tcp_opts.timeout = opts.timeout or default_timeout
|
|
|
|
tcp_opts.callback = mail_cb
|
|
|
|
|
|
|
|
if not rspamd_tcp.request(tcp_opts) then
|
2019-05-25 15:12:44 +02:00
|
|
|
callback(false, 'cannot make a TCP connection')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
exports.sendmail = sendmail
|
|
|
|
|
|
|
|
return exports
|