You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

lua_smtp.lua 5.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. --[[
  2. Copyright (c) 2022, Vsevolod Stakhov <vsevolod@rspamd.com>
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. ]]--
  13. local rspamd_tcp = require "rspamd_tcp"
  14. local lua_util = require "lua_util"
  15. local exports = {}
  16. local CRLF = '\r\n'
  17. local default_timeout = 10.0
  18. --[[[
  19. -- @function lua_smtp.sendmail(task, message, opts, callback)
  20. --]]
  21. local function sendmail(opts, message, callback)
  22. local stage = 'connect'
  23. local function mail_cb(err, data, conn)
  24. local function no_error_write(merr)
  25. if merr then
  26. callback(false, string.format('error on stage %s: %s',
  27. stage, merr))
  28. if conn then
  29. conn:close()
  30. end
  31. return false
  32. end
  33. return true
  34. end
  35. local function no_error_read(merr, mdata, wantcode)
  36. wantcode = wantcode or '2'
  37. if merr then
  38. callback(false, string.format('error on stage %s: %s',
  39. stage, merr))
  40. if conn then
  41. conn:close()
  42. end
  43. return false
  44. end
  45. if mdata then
  46. if type(mdata) ~= 'string' then
  47. mdata = tostring(mdata)
  48. end
  49. if string.sub(mdata, 1, 1) ~= wantcode then
  50. callback(false, string.format('bad smtp response on stage %s: "%s" when "%s" expected',
  51. stage, mdata, wantcode))
  52. if conn then
  53. conn:close()
  54. end
  55. return false
  56. end
  57. else
  58. callback(false, string.format('no data on stage %s',
  59. stage))
  60. if conn then
  61. conn:close()
  62. end
  63. return false
  64. end
  65. return true
  66. end
  67. -- After quit
  68. local function all_done_cb(merr, mdata)
  69. if conn then
  70. conn:close()
  71. end
  72. callback(true, nil)
  73. return true
  74. end
  75. -- QUIT stage
  76. local function quit_done_cb(_, _)
  77. conn:add_read(all_done_cb, CRLF)
  78. end
  79. local function quit_cb(merr, mdata)
  80. if no_error_read(merr, mdata) then
  81. conn:add_write(quit_done_cb, 'QUIT' .. CRLF)
  82. end
  83. end
  84. local function pre_quit_cb(merr, _)
  85. if no_error_write(merr) then
  86. stage = 'quit'
  87. conn:add_read(quit_cb, CRLF)
  88. end
  89. end
  90. -- DATA stage
  91. local function data_done_cb(merr, mdata)
  92. if no_error_read(merr, mdata, '3') then
  93. if type(message) == 'string' or type(message) == 'userdata' then
  94. conn:add_write(pre_quit_cb, { message, CRLF .. '.' .. CRLF })
  95. else
  96. table.insert(message, CRLF .. '.' .. CRLF)
  97. conn:add_write(pre_quit_cb, message)
  98. end
  99. end
  100. end
  101. local function data_cb(merr, _)
  102. if no_error_write(merr) then
  103. conn:add_read(data_done_cb, CRLF)
  104. end
  105. end
  106. -- RCPT phase
  107. local next_recipient
  108. local function rcpt_done_cb_gen(i)
  109. return function(merr, mdata)
  110. if no_error_read(merr, mdata) then
  111. if i == #opts.recipients then
  112. conn:add_write(data_cb, 'DATA' .. CRLF)
  113. else
  114. next_recipient(i + 1)
  115. end
  116. end
  117. end
  118. end
  119. local function rcpt_cb_gen(i)
  120. return function(merr, _)
  121. if no_error_write(merr, '2') then
  122. conn:add_read(rcpt_done_cb_gen(i), CRLF)
  123. end
  124. end
  125. end
  126. next_recipient = function(i)
  127. conn:add_write(rcpt_cb_gen(i),
  128. string.format('RCPT TO: <%s>%s', opts.recipients[i], CRLF))
  129. end
  130. -- FROM stage
  131. local function from_done_cb(merr, mdata)
  132. -- We need to iterate over recipients sequentially
  133. if no_error_read(merr, mdata, '2') then
  134. stage = 'rcpt'
  135. next_recipient(1)
  136. end
  137. end
  138. local function from_cb(merr, _)
  139. if no_error_write(merr) then
  140. conn:add_read(from_done_cb, CRLF)
  141. end
  142. end
  143. local function hello_done_cb(merr, mdata)
  144. if no_error_read(merr, mdata) then
  145. stage = 'from'
  146. conn:add_write(from_cb, string.format(
  147. 'MAIL FROM: <%s>%s', opts.from, CRLF))
  148. end
  149. end
  150. -- HELO stage
  151. local function hello_cb(merr)
  152. if no_error_write(merr) then
  153. conn:add_read(hello_done_cb, CRLF)
  154. end
  155. end
  156. if no_error_read(err, data) then
  157. stage = 'helo'
  158. conn:add_write(hello_cb, string.format('HELO %s%s',
  159. opts.helo, CRLF))
  160. end
  161. end
  162. if type(opts.recipients) == 'string' then
  163. opts.recipients = { opts.recipients }
  164. end
  165. local tcp_opts = lua_util.shallowcopy(opts)
  166. tcp_opts.stop_pattern = CRLF
  167. tcp_opts.timeout = opts.timeout or default_timeout
  168. tcp_opts.callback = mail_cb
  169. if not rspamd_tcp.request(tcp_opts) then
  170. callback(false, 'cannot make a TCP connection')
  171. end
  172. end
  173. exports.sendmail = sendmail
  174. return exports