2003-10-21 03:12:23 +02:00
|
|
|
-- make sure LuaSocket is loaded
|
|
|
|
if not LUASOCKET_LIBNAME then error('module requires LuaSocket') end
|
|
|
|
-- get LuaSocket namespace
|
|
|
|
local socket = _G[LUASOCKET_LIBNAME]
|
|
|
|
if not socket then error('module requires LuaSocket') end
|
|
|
|
-- create smtp namespace inside LuaSocket namespace
|
2004-02-04 15:29:11 +01:00
|
|
|
local smtp = socket.smtp or {}
|
2003-10-21 03:12:23 +02:00
|
|
|
socket.smtp = smtp
|
|
|
|
-- make all module globals fall into smtp namespace
|
|
|
|
setmetatable(smtp, { __index = _G })
|
|
|
|
setfenv(1, smtp)
|
|
|
|
|
|
|
|
-- default port
|
|
|
|
PORT = 25
|
2001-09-12 20:16:31 +02:00
|
|
|
-- domain used in HELO command and default sendmail
|
|
|
|
-- If we are under a CGI, try to get from environment
|
2003-10-21 03:12:23 +02:00
|
|
|
DOMAIN = os.getenv("SERVER_NAME") or "localhost"
|
2001-09-12 20:16:31 +02:00
|
|
|
-- default server used to send e-mails
|
2003-10-21 03:12:23 +02:00
|
|
|
SERVER = "localhost"
|
|
|
|
|
2004-02-04 15:29:11 +01:00
|
|
|
function stuff()
|
|
|
|
return socket.cicle(dot, 2)
|
|
|
|
end
|
|
|
|
|
2003-10-21 03:12:23 +02:00
|
|
|
-- tries to get a pattern from the server and closes socket on error
|
|
|
|
local function try_receiving(connection, pattern)
|
|
|
|
local data, message = connection:receive(pattern)
|
|
|
|
if not data then connection:close() end
|
|
|
|
print(data)
|
|
|
|
return data, message
|
2001-09-12 20:16:31 +02:00
|
|
|
end
|
|
|
|
|
2003-10-21 03:12:23 +02:00
|
|
|
-- tries to send data to server and closes socket on error
|
|
|
|
local function try_sending(connection, data)
|
|
|
|
local sent, message = connection:send(data)
|
|
|
|
if not sent then connection:close() end
|
|
|
|
io.write(data)
|
|
|
|
return sent, message
|
2001-09-12 20:16:31 +02:00
|
|
|
end
|
|
|
|
|
2003-10-21 03:12:23 +02:00
|
|
|
-- gets server reply
|
|
|
|
local function get_reply(connection)
|
|
|
|
local code, current, separator, _
|
|
|
|
local line, message = try_receiving(connection)
|
|
|
|
local reply = line
|
|
|
|
if message then return nil, message end
|
|
|
|
_, _, code, separator = string.find(line, "^(%d%d%d)(.?)")
|
|
|
|
if not code then return nil, "invalid server reply" end
|
|
|
|
if separator == "-" then -- reply is multiline
|
|
|
|
repeat
|
|
|
|
line, message = try_receiving(connection)
|
|
|
|
if message then return nil, message end
|
|
|
|
_,_, current, separator = string.find(line, "^(%d%d%d)(.)")
|
|
|
|
if not current or not separator then
|
|
|
|
return nil, "invalid server reply"
|
|
|
|
end
|
|
|
|
reply = reply .. "\n" .. line
|
|
|
|
-- reply ends with same code
|
|
|
|
until code == current and separator == " "
|
2001-03-27 21:25:11 +02:00
|
|
|
end
|
2003-10-21 03:12:23 +02:00
|
|
|
return code, reply
|
2001-03-27 21:25:11 +02:00
|
|
|
end
|
|
|
|
|
2003-10-21 03:12:23 +02:00
|
|
|
-- metatable for server connection object
|
|
|
|
local metatable = { __index = {} }
|
|
|
|
|
|
|
|
-- switch handler for execute function
|
|
|
|
local switch = {}
|
|
|
|
|
|
|
|
-- execute the "check" instruction
|
|
|
|
function switch.check(connection, instruction)
|
|
|
|
local code, reply = get_reply(connection)
|
|
|
|
if not code then return nil, reply end
|
|
|
|
if type(instruction.check) == "function" then
|
|
|
|
return instruction.check(code, reply)
|
|
|
|
else
|
|
|
|
if string.find(code, instruction.check) then return code, reply
|
|
|
|
else return nil, reply end
|
2001-03-27 21:25:11 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2003-10-21 03:12:23 +02:00
|
|
|
-- stub for invalid instructions
|
|
|
|
function switch.invalid(connection, instruction)
|
|
|
|
return nil, "invalid instruction"
|
2001-03-27 21:25:11 +02:00
|
|
|
end
|
|
|
|
|
2003-10-21 03:12:23 +02:00
|
|
|
-- execute the "command" instruction
|
|
|
|
function switch.command(connection, instruction)
|
|
|
|
local line
|
|
|
|
if instruction.argument then
|
|
|
|
line = instruction.command .. " " .. instruction.argument .. "\r\n"
|
|
|
|
else line = instruction.command .. "\r\n" end
|
|
|
|
return try_sending(connection, line)
|
2001-09-12 20:16:31 +02:00
|
|
|
end
|
|
|
|
|
2003-10-21 03:12:23 +02:00
|
|
|
function switch.raw(connection, instruction)
|
|
|
|
if type(instruction.raw) == "function" then
|
|
|
|
local f = instruction.raw
|
|
|
|
while true do
|
|
|
|
local chunk, new_f = f()
|
|
|
|
if not chunk then return nil, new_f end
|
|
|
|
if chunk == "" then return true end
|
|
|
|
f = new_f or f
|
|
|
|
local code, message = try_sending(connection, chunk)
|
|
|
|
if not code then return nil, message end
|
|
|
|
end
|
|
|
|
else return try_sending(connection, instruction.raw) end
|
2001-03-27 21:25:11 +02:00
|
|
|
end
|
|
|
|
|
2003-10-21 03:12:23 +02:00
|
|
|
-- finds out what instruction are we dealing with
|
|
|
|
local function instruction_type(instruction)
|
|
|
|
if type(instruction) ~= "table" then return "invalid" end
|
|
|
|
if instruction.command then return "command" end
|
|
|
|
if instruction.check then return "check" end
|
|
|
|
if instruction.raw then return "raw" end
|
|
|
|
return "invalid"
|
2001-03-27 21:25:11 +02:00
|
|
|
end
|
|
|
|
|
2003-10-21 03:12:23 +02:00
|
|
|
-- execute a list of instructions
|
|
|
|
function metatable.__index:execute(instructions)
|
|
|
|
if type(instructions) ~= "table" then error("instruction expected", 1) end
|
|
|
|
if not instructions[1] then instructions = { instructions } end
|
|
|
|
local code, message
|
|
|
|
for _, instruction in ipairs(instructions) do
|
|
|
|
local type = instruction_type(instruction)
|
|
|
|
code, message = switch[type](self.connection, instruction)
|
|
|
|
if not code then break end
|
2001-03-27 21:25:11 +02:00
|
|
|
end
|
2003-10-21 03:12:23 +02:00
|
|
|
return code, message
|
2001-03-27 21:25:11 +02:00
|
|
|
end
|
|
|
|
|
2003-10-21 03:12:23 +02:00
|
|
|
-- closes the underlying connection
|
|
|
|
function metatable.__index:close()
|
|
|
|
self.connection:close()
|
2001-03-27 21:25:11 +02:00
|
|
|
end
|
|
|
|
|
2003-10-21 03:12:23 +02:00
|
|
|
-- connect with server and return a smtp connection object
|
|
|
|
function connect(host)
|
|
|
|
local connection, message = socket.connect(host, PORT)
|
|
|
|
if not connection then return nil, message end
|
|
|
|
return setmetatable({ connection = connection }, metatable)
|
2001-03-27 21:25:11 +02:00
|
|
|
end
|
|
|
|
|
2003-10-21 03:12:23 +02:00
|
|
|
-- simple test drive
|
|
|
|
|
|
|
|
--[[
|
|
|
|
c, m = connect("localhost")
|
|
|
|
assert(c, m)
|
|
|
|
assert(c:execute {check = "2.." })
|
|
|
|
assert(c:execute {{command = "EHLO", argument = "localhost"}, {check = "2.."}})
|
|
|
|
assert(c:execute {command = "MAIL", argument = "FROM:<diego@princeton.edu>"})
|
|
|
|
assert(c:execute {check = "2.."})
|
|
|
|
assert(c:execute {command = "RCPT", argument = "TO:<diego@cs.princeton.edu>"})
|
|
|
|
assert(c:execute {check = function (code) return code == "250" end})
|
|
|
|
assert(c:execute {{command = "DATA"}, {check = "3.."}})
|
|
|
|
assert(c:execute {{raw = "This is the message\r\n.\r\n"}, {check = "2.."}})
|
|
|
|
assert(c:execute {{command = "QUIT"}, {check = "2.."}})
|
|
|
|
c:close()
|
|
|
|
]]
|
|
|
|
|
|
|
|
return smtp
|