Initial revision

This commit is contained in:
Diego Nehab 2000-12-29 22:15:09 +00:00
parent 6f9d15b660
commit 17c4d1c305
12 changed files with 1777 additions and 0 deletions

437
src/ftp.lua Normal file
View file

@ -0,0 +1,437 @@
-----------------------------------------------------------------------------
-- Simple FTP support for the Lua language using the LuaSocket toolkit.
-- Author: Diego Nehab
-- Date: 26/12/2000
-- Conforming to: RFC 959
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Program constants
-----------------------------------------------------------------------------
-- timeout in seconds before the program gives up on a connection
local TIMEOUT = 60
-- default port for ftp service
local PORT = 21
-- this is the default anonymous password. used when no password is
-- provided in url. should be changed for your e-mail.
local EMAIL = "anonymous@anonymous.org"
-----------------------------------------------------------------------------
-- Parses a url and returns its scheme, user, password, host, port
-- and path components, according to RFC 1738, Uniform Resource Locators (URL),
-- of December 1994
-- Input
-- url: unique resource locator desired
-- default: table containing default values to be returned
-- Returns
-- table with the following fields:
-- host: host to connect
-- path: url path
-- port: host port to connect
-- user: user name
-- pass: password
-- scheme: protocol
-----------------------------------------------------------------------------
local split_url = function(url, default)
-- initialize default parameters
local parsed = default or {}
-- get scheme
url = gsub(url, "^(.+)://", function (s) %parsed.scheme = s end)
-- get user name and password. both can be empty!
-- moreover, password can be ommited
url = gsub(url, "^([^@:/]*)(:?)([^:@/]-)@", function (u, c, p)
%parsed.user = u
-- there can be an empty password, but the ':' has to be there
-- or else there is no password
%parsed.pass = nil -- kill default password
if c == ":" then %parsed.pass = p end
end)
-- get host
url = gsub(url, "^([%w%.%-]+)", function (h) %parsed.host = h end)
-- get port if any
url = gsub(url, "^:(%d+)", function (p) %parsed.port = p end)
-- whatever is left is the path
if url ~= "" then parsed.path = url end
return parsed
end
-----------------------------------------------------------------------------
-- Gets ip and port for data connection from PASV answer
-- Input
-- pasv: PASV command answer
-- Returns
-- ip: string containing ip for data connection
-- port: port for data connection
-----------------------------------------------------------------------------
local get_pasv = function(pasv)
local a,b,c,d,p1,p2
local ip, port
_,_, a, b, c, d, p1, p2 =
strfind(pasv, "(%d*),(%d*),(%d*),(%d*),(%d*),(%d*)")
if not a or not b or not c or not d or not p1 or not p2 then
return nil, nil
end
ip = format("%d.%d.%d.%d", a, b, c, d)
port = tonumber(p1)*256 + tonumber(p2)
return ip, port
end
-----------------------------------------------------------------------------
-- Sends a FTP command through socket
-- Input
-- control: control connection socket
-- cmd: command
-- arg: command argument if any
-----------------------------------------------------------------------------
local send_command = function(control, cmd, arg)
local line, err
if arg then line = cmd .. " " .. arg .. "\r\n"
else line = cmd .. "\r\n" end
err = control:send(line)
return err
end
-----------------------------------------------------------------------------
-- Gets FTP command answer, unfolding if neccessary
-- Input
-- control: control connection socket
-- Returns
-- answer: whole server reply, nil if error
-- code: answer status code or error message
-----------------------------------------------------------------------------
local get_answer = function(control)
local code, lastcode, sep
local line, err = control:receive()
local answer = line
if err then return nil, err end
_,_, code, sep = strfind(line, "^(%d%d%d)(.)")
if not code or not sep then return nil, answer end
if sep == "-" then -- answer is multiline
repeat
line, err = control:receive()
if err then return nil, err end
_,_, lastcode, sep = strfind(line, "^(%d%d%d)(.)")
answer = answer .. "\n" .. line
until code == lastcode and sep == " " -- answer ends with same code
end
return answer, tonumber(code)
end
-----------------------------------------------------------------------------
-- Checks if a message return is correct. Closes control connection if not.
-- Input
-- control: control connection socket
-- success: table with successfull reply status code
-- Returns
-- code: reply code or nil in case of error
-- answer: server complete answer or system error message
-----------------------------------------------------------------------------
local check_answer = function(control, success)
local answer, code = %get_answer(control)
if not answer then
control:close()
return nil, code
end
if type(success) ~= "table" then success = {success} end
for i = 1, getn(success) do
if code == success[i] then
return code, answer
end
end
control:close()
return nil, answer
end
-----------------------------------------------------------------------------
-- Trys a command on control socked, in case of error, the control connection
-- is closed.
-- Input
-- control: control connection socket
-- cmd: command
-- arg: command argument or nil if no argument
-- success: table with successfull reply status code
-- Returns
-- code: reply code or nil in case of error
-- answer: server complete answer or system error message
-----------------------------------------------------------------------------
local try_command = function(control, cmd, arg, success)
local err = %send_command(control, cmd, arg)
if err then
control:close()
return nil, err
end
local code, answer = %check_answer(control, success)
if not code then return nil, answer end
return code, answer
end
-----------------------------------------------------------------------------
-- Creates a table with all directories in path
-- Input
-- file: abolute path to file
-- Returns
-- file: filename
-- path: table with directories to reach filename
-- isdir: is it a directory or a file
-----------------------------------------------------------------------------
local split_path = function(file)
local path = {}
local isdir
file = file or "/"
-- directory ends with a '/'
_,_, isdir = strfind(file, "([/])$")
gsub(file, "([^/]+)", function (dir) tinsert(%path, dir) end)
if not isdir then file = tremove(path)
else file = nil end
return file, path, isdir
end
-----------------------------------------------------------------------------
-- Check server greeting
-- Input
-- control: control connection with server
-- Returns
-- code: nil if error
-- answer: server answer or error message
-----------------------------------------------------------------------------
local check_greeting = function(control)
local code, answer = %check_answer(control, {120, 220})
if not code then return nil, answer end
if code == 120 then -- please try again, somewhat busy now...
code, answer = %check_answer(control, {220})
end
return code, answer
end
-----------------------------------------------------------------------------
-- Log in on server
-- Input
-- control: control connection with server
-- user: user name
-- pass: user password if any
-- Returns
-- code: nil if error
-- answer: server answer or error message
-----------------------------------------------------------------------------
local login = function(control, user, pass)
local code, answer = %try_command(control, "user", parsed.user, {230, 331})
if not code then return nil, answer end
if code == 331 and parsed.pass then -- need pass and we have pass
code, answer = %try_command(control, "pass", parsed.pass, {230, 202})
end
return code, answer
end
-----------------------------------------------------------------------------
-- Change to target directory
-- Input
-- control: socket for control connection with server
-- path: array with directories in order
-- Returns
-- code: nil if error
-- answer: server answer or error message
-----------------------------------------------------------------------------
local cwd = function(control, path)
local code, answer = 250, "Home directory used"
for i = 1, getn(path) do
code, answer = %try_command(control, "cwd", path[i], {250})
if not code then return nil, answer end
end
return code, answer
end
-----------------------------------------------------------------------------
-- Start data connection with server
-- Input
-- control: control connection with server
-- Returns
-- data: socket for data connection with server, nil if error
-- answer: server answer or error message
-----------------------------------------------------------------------------
local start_dataconnection = function(control)
-- ask for passive data connection
local code, answer = %try_command(control, "pasv", nil, {227})
if not code then return nil, answer end
-- get data connection parameters from server reply
local host, port = %get_pasv(answer)
if not host or not port then return nil, answer end
-- start data connection with given parameters
local data, err = connect(host, port)
if not data then return nil, err end
data:timeout(%TIMEOUT)
return data
end
-----------------------------------------------------------------------------
-- Closes control connection with server
-- Input
-- control: control connection with server
-- Returns
-- code: nil if error
-- answer: server answer or error message
-----------------------------------------------------------------------------
local logout = function(control)
local code, answer = %try_command(control, "quit", nil, {221})
if not code then return nil, answer end
control:close()
return code, answer
end
-----------------------------------------------------------------------------
-- Retrieves file or directory listing
-- Input
-- control: control connection with server
-- data: data connection with server
-- file: file name under current directory
-- isdir: is file a directory name?
-- Returns
-- file: string with file contents, nil if error
-- answer: server answer or error message
-----------------------------------------------------------------------------
local retrieve_file = function(control, data, file, isdir)
-- ask server for file or directory listing accordingly
if isdir then code, answer = %try_command(control, "nlst", file, {150, 125})
else code, answer = %try_command(control, "retr", file, {150, 125}) end
if not code then
control:close()
data:close()
return nil, answer
end
-- download whole file
file, err = data:receive("*a")
data:close()
if err then
control:close()
return nil, err
end
-- make sure file transfered ok
code, answer = %check_answer(control, {226, 250})
if not code then return nil, answer
else return file, answer end
end
-----------------------------------------------------------------------------
-- Stores a file
-- Input
-- control: control connection with server
-- data: data connection with server
-- file: file name under current directory
-- bytes: file contents in string
-- Returns
-- file: string with file contents, nil if error
-- answer: server answer or error message
-----------------------------------------------------------------------------
local store_file = function (control, data, file, bytes)
local code, answer = %try_command(control, "stor", file, {150, 125})
if not code then
data:close()
return nil, answer
end
-- send whole file and close connection to mark file end
answer = data:send(bytes)
data:close()
if answer then
control:close()
return nil, answer
end
-- check if file was received right
return %check_answer(control, {226, 250})
end
-----------------------------------------------------------------------------
-- Change transfer type
-- Input
-- control: control connection with server
-- type: new transfer type
-- Returns
-- code: nil if error
-- answer: server answer or error message
-----------------------------------------------------------------------------
local change_type = function(control, type)
if type == "b" then type = "i" else type = "a" end
return %try_command(control, "type", type, {200})
end
-----------------------------------------------------------------------------
-- Retrieve a file from a ftp server
-- Input
-- url: file location
-- type: "binary" or "ascii"
-- Returns
-- file: downloaded file or nil in case of error
-- err: error message if any
-----------------------------------------------------------------------------
function ftp_get(url, type)
local control, data, err
local answer, code, server, file, path
parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL})
-- start control connection
control, err = connect(parsed.host, parsed.port)
if not control then return nil, err end
control:timeout(%TIMEOUT)
-- get and check greeting
code, answer = %check_greeting(control)
if not code then return nil, answer end
-- try to log in
code, answer = %login(control, parsed.user, parsed.pass)
if not code then return nil, answer end
-- go to directory
file, path, isdir = %split_path(parsed.path)
code, answer = %cwd(control, path)
if not code then return nil, answer end
-- change to binary type?
code, answer = %change_type(control, type)
if not code then return nil, answer end
-- start data connection
data, answer = %start_dataconnection(control)
if not data then return nil, answer end
-- ask server to send file or directory listing
file, answer = %retrieve_file(control, data, file, isdir)
if not file then return nil, answer end
-- disconnect
%logout(control)
-- return whatever file we received plus a possible error
return file, answer
end
-----------------------------------------------------------------------------
-- Uploads a file to a FTP server
-- Input
-- url: file location
-- bytes: file contents
-- type: "binary" or "ascii"
-- Returns
-- err: error message if any
-----------------------------------------------------------------------------
function ftp_put(url, bytes, type)
local control, data
local answer, code, server, file, path
parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL})
-- start control connection
control, answer = connect(parsed.host, parsed.port)
if not control then return answer end
control:timeout(%TIMEOUT)
-- get and check greeting
code, answer = %check_greeting(control)
if not code then return answer end
-- try to log in
code, answer = %login(control, parsed.user, parsed.pass)
if not code then return answer end
-- go to directory
file, path, isdir = %split_path(parsed.path)
code, answer = %cwd(control, path)
if not code then return answer end
-- change to binary type?
code, answer = %change_type(control, type)
if not code then return answer end
-- start data connection
data, answer = %start_dataconnection(control)
if not data then return answer end
-- ask server to send file or directory listing
code, answer = %store_file(control, data, file, bytes)
if not code then return answer end
-- disconnect
%logout(control)
-- return whatever file we received plus a possible error
return nil
end

312
src/http.lua Normal file
View file

@ -0,0 +1,312 @@
-----------------------------------------------------------------------------
-- Simple HTTP/1.1 support for the Lua language using the LuaSocket toolkit.
-- Author: Diego Nehab
-- Date: 26/12/2000
-- Conforming to: RFC 2068
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Program constants
-----------------------------------------------------------------------------
-- connection timeout in seconds
local TIMEOUT = 60
-- default port for document retrieval
local PORT = 80
-- user agent field sent in request
local USERAGENT = "LuaSocket/HTTP 1.0"
-----------------------------------------------------------------------------
-- Tries to get a line from the server or close socket if error
-- sock: socket connected to the server
-- Returns
-- line: line received or nil in case of error
-- err: error message if any
-----------------------------------------------------------------------------
local try_getline = function(sock)
line, err = sock:receive()
if err then
sock:close()
return nil, err
end
return line
end
-----------------------------------------------------------------------------
-- Tries to send a line to the server or close socket if error
-- sock: socket connected to the server
-- line: line to send
-- Returns
-- err: error message if any
-----------------------------------------------------------------------------
local try_sendline = function(sock, line)
err = sock:send(line)
if err then sock:close() end
return err
end
-----------------------------------------------------------------------------
-- Retrieves status from http reply
-- Input
-- reply: http reply string
-- Returns
-- status: integer with status code
-----------------------------------------------------------------------------
local get_status = function(reply)
local _,_, status = strfind(reply, " (%d%d%d) ")
return tonumber(status)
end
-----------------------------------------------------------------------------
-- Receive server reply messages
-- Input
-- sock: server socket
-- Returns
-- status: server reply status code or nil if error
-- reply: full server reply
-- err: error message if any
-----------------------------------------------------------------------------
local get_reply = function(sock)
local reply, err
reply, err = %try_getline(sock)
if not err then return %get_status(reply), reply
else return nil, nil, err end
end
-----------------------------------------------------------------------------
-- Receive and parse mime headers
-- Input
-- sock: server socket
-- mime: a table that might already contain mime headers
-- Returns
-- mime: a table with all mime headers in the form
-- {name_1 = "value_1", name_2 = "value_2" ... name_n = "value_n"}
-- all name_i are lowercase
-- nil and error message in case of error
-----------------------------------------------------------------------------
local get_mime = function(sock, mime)
local line, err
local name, value
-- get first line
line, err = %try_getline(sock)
if err then return nil, err end
-- headers go until a blank line is found
while line ~= "" do
-- get field-name and value
_,_, name, value = strfind(line, "(.-):%s*(.*)")
name = strlower(name)
-- get next line (value might be folded)
line, err = %try_getline(sock)
if err then return nil, err end
-- unfold any folded values
while not err and line ~= "" and (strsub(line, 1, 1) == " ") do
value = value .. line
line, err = %try_getline(sock)
if err then return nil, err end
end
-- save pair in table
if mime[name] then
-- join any multiple field
mime[name] = mime[name] .. ", " .. value
else
-- new field
mime[name] = value
end
end
return mime
end
-----------------------------------------------------------------------------
-- Receives http body
-- Input
-- sock: server socket
-- mime: initial mime headers
-- Returns
-- body: a string containing the body of the document
-- nil and error message in case of error
-- Obs:
-- mime: headers might be modified by chunked transfer
-----------------------------------------------------------------------------
local get_body = function(sock, mime)
local body, err
if mime["transfer-encoding"] == "chunked" then
local chunk_size, line
body = ""
repeat
-- get chunk size, skip extention
line, err = %try_getline(sock)
if err then return nil, err end
chunk_size = tonumber(gsub(line, ";.*", ""), 16)
if not chunk_size then
sock:close()
return nil, "invalid chunk size"
end
-- get chunk
line, err = sock:receive(chunk_size)
if err then
sock:close()
return nil, err
end
-- concatenate new chunk
body = body .. line
-- skip blank line
_, err = %try_getline(sock)
if err then return nil, err end
until chunk_size <= 0
-- store extra mime headers
--_, err = %get_mime(sock, mime)
--if err then return nil, err end
elseif mime["content-length"] then
body, err = sock:receive(tonumber(mime["content-length"]))
if err then
sock:close()
return nil, err
end
else
-- get it all until connection closes!
body, err = sock:receive("*a")
if err then
sock:close()
return nil, err
end
end
-- return whole body
return body
end
-----------------------------------------------------------------------------
-- Parses a url and returns its scheme, user, password, host, port
-- and path components, according to RFC 1738, Uniform Resource Locators (URL),
-- of December 1994
-- Input
-- url: unique resource locator desired
-- default: table containing default values to be returned
-- Returns
-- table with the following fields:
-- host: host to connect
-- path: url path
-- port: host port to connect
-- user: user name
-- pass: password
-- scheme: protocol
-----------------------------------------------------------------------------
local split_url = function(url, default)
-- initialize default parameters
local parsed = default or {}
-- get scheme
url = gsub(url, "^(.+)://", function (s) %parsed.scheme = s end)
-- get user name and password. both can be empty!
-- moreover, password can be ommited
url = gsub(url, "^([^@:/]*)(:?)([^:@/]-)@", function (u, c, p)
%parsed.user = u
-- there can be an empty password, but the ':' has to be there
-- or else there is no password
%parsed.pass = nil -- kill default password
if c == ":" then %parsed.pass = p end
end)
-- get host
url = gsub(url, "^([%w%.%-]+)", function (h) %parsed.host = h end)
-- get port if any
url = gsub(url, "^:(%d+)", function (p) %parsed.port = p end)
-- whatever is left is the path
if url ~= "" then parsed.path = url end
return parsed
end
-----------------------------------------------------------------------------
-- Sends a GET message through socket
-- Input
-- socket: http connection socket
-- path: path requested
-- mime: mime headers to send in request
-- Returns
-- err: nil in case of success, error message otherwise
-----------------------------------------------------------------------------
local send_get = function(sock, path, mime)
local err = %try_sendline(sock, "GET " .. path .. " HTTP/1.1\r\n")
if err then return err end
for i, v in mime do
err = %try_sendline(sock, i .. ": " .. v .. "\r\n")
if err then return err end
end
err = %try_sendline(sock, "\r\n")
return err
end
-----------------------------------------------------------------------------
-- Converts field names to lowercase
-- Input
-- headers: user header fields
-- parsed: parsed url components
-- Returns
-- mime: a table with the same headers, but with lowercase field names
-----------------------------------------------------------------------------
local fill_headers = function(headers, parsed)
local mime = {}
headers = headers or {}
for i,v in headers do
mime[strlower(i)] = v
end
mime["connection"] = "close"
mime["host"] = parsed.host
mime["user-agent"] = %USERAGENT
if parsed.user and parsed.pass then -- Basic Authentication
mime["authorization"] = "Basic "..
base64(parsed.user .. ":" .. parsed.pass)
end
return mime
end
-----------------------------------------------------------------------------
-- We need base64 convertion routines for Basic Authentication Scheme
-----------------------------------------------------------------------------
dofile("base64.lua")
-----------------------------------------------------------------------------
-- Downloads and receives a http url, with its mime headers
-- Input
-- url: unique resource locator desired
-- headers: headers to send with request
-- tried: is this an authentication retry?
-- Returns
-- body: document body, if successfull
-- mime: headers received with document, if sucessfull
-- reply: server reply, if successfull
-- err: error message, if any
-----------------------------------------------------------------------------
function http_get(url, headers)
local sock, err, mime, body, status, reply
-- get url components
local parsed = %split_url(url, {port = %PORT, path ="/"})
-- fill default headers
headers = %fill_headers(headers, parsed)
-- try connection
sock, err = connect(parsed.host, parsed.port)
if not sock then return nil, nil, nil, err end
-- set connection timeout
sock:timeout(%TIMEOUT)
-- send request
err = %send_get(sock, parsed.path, headers)
if err then return nil, nil, nil, err end
-- get server message
status, reply, err = %get_reply(sock)
if err then return nil, nil, nil, err end
-- get url accordingly
if status == 200 then -- ok, go on and get it
mime, err = %get_mime(sock, {})
if err then return nil, nil, reply, err end
body, err = %get_body(sock, mime)
if err then return nil, mime, reply, err end
sock:close()
return body, mime, reply
elseif status == 301 then -- moved permanently, try again
mime = %get_mime(sock, {})
sock:close()
if mime["location"] then return http_get(mime["location"], headers)
else return nil, mime, reply end
elseif status == 401 then
mime, err = %get_mime(sock, {})
if err then return nil, nil, reply, err end
return nil, mime, reply
end
return nil, nil, reply
end

18
src/luasocket.h Normal file
View file

@ -0,0 +1,18 @@
/*=========================================================================*\
* TCP/IP support for LUA
* Diego Nehab
* 9/11/1999
\*=========================================================================*/
#ifndef _LUASOCKET_H_
#define _LUASOCKET_H_
/*=========================================================================*\
* Exported function declarations
\*=========================================================================*/
/*-------------------------------------------------------------------------*\
* Initializes toolkit
\*-------------------------------------------------------------------------*/
void lua_socketlibopen(lua_State *L);
#endif /* _LUASOCKET_H_ */

338
src/smtp.lua Normal file
View file

@ -0,0 +1,338 @@
-----------------------------------------------------------------------------
-- Simple SMTP support for the Lua language using the LuaSocket toolkit.
-- Author: Diego Nehab
-- Date: 26/12/2000
-- Conforming to: RFC 821
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Program constants
-----------------------------------------------------------------------------
-- timeout in secconds before we give up waiting
local TIMEOUT = 180
-- port used for connection
local PORT = 25
-- domain used in HELO command. If we are under a CGI, try to get from
-- environment
local DOMAIN = getenv("SERVER_NAME")
if not DOMAIN then
DOMAIN = "localhost"
end
-----------------------------------------------------------------------------
-- Tries to send DOS mode lines. Closes socket on error.
-- Input
-- sock: server socket
-- line: string to be sent
-- Returns
-- err: message in case of error, nil if successfull
-----------------------------------------------------------------------------
local puts = function(sock, line)
local err = sock:send(line .. "\r\n")
if err then sock:close() end
return err
end
-----------------------------------------------------------------------------
-- Tries to receive DOS mode lines. Closes socket on error.
-- Input
-- sock: server socket
-- Returns
-- line: received string if successfull, nil in case of error
-- err: error message if any
-----------------------------------------------------------------------------
local gets = function(sock)
local line, err = sock:receive("*l")
if err then
sock:close()
return nil, err
end
return line
end
-----------------------------------------------------------------------------
-- Gets a reply from the server and close connection if it is wrong
-- Input
-- sock: server socket
-- accept: acceptable errorcodes
-- Returns
-- code: server reply code. nil if error
-- line: complete server reply message or error message
-----------------------------------------------------------------------------
local get_reply = function(sock, accept)
local line, err = %gets(sock)
if line then
if type(accept) ~= "table" then accept = {accept} end
local _,_, code = strfind(line, "^(%d%d%d)")
if not code then return nil, line end
code = tonumber(code)
for i = 1, getn(accept) do
if code == accept[i] then return code, line end
end
sock:close()
return nil, line
end
return nil, err
end
-----------------------------------------------------------------------------
-- Sends a command to the server
-- Input
-- sock: server socket
-- command: command to be sent
-- param: command parameters if any
-- Returns
-- err: error message if any
-----------------------------------------------------------------------------
local send_command = function(sock, command, param)
local line
if param then line = command .. " " .. param
else line = command end
return %puts(sock, line)
end
-----------------------------------------------------------------------------
-- Gets the initial server greeting
-- Input
-- sock: server socket
-- Returns
-- code: server status code, nil if error
-- answer: complete server reply
-----------------------------------------------------------------------------
local get_helo = function(sock)
return %get_reply(sock, 220)
end
-----------------------------------------------------------------------------
-- Sends initial client greeting
-- Input
-- sock: server socket
-- Returns
-- code: server status code, nil if error
-- answer: complete server reply
-----------------------------------------------------------------------------
local send_helo = function(sock)
local err = %send_command(sock, "HELO", %DOMAIN)
if not err then
return %get_reply(sock, 250)
else return nil, err end
end
-----------------------------------------------------------------------------
-- Sends mime headers
-- Input
-- sock: server socket
-- mime: table with mime headers to be sent
-- Returns
-- err: error message if any
-----------------------------------------------------------------------------
local send_mime = function(sock, mime)
local err
mime = mime or {}
-- send all headers
for name,value in mime do
err = sock:send(name .. ": " .. value .. "\r\n")
if err then
sock:close()
return err
end
end
-- end mime part
err = sock:send("\r\n")
if err then sock:close() end
return err
end
-----------------------------------------------------------------------------
-- Sends connection termination command
-- Input
-- sock: server socket
-- Returns
-- code: server status code, nil if error
-- answer: complete server reply
-----------------------------------------------------------------------------
local send_quit = function(sock)
local code, answer
local err = %send_command(sock, "QUIT")
if not err then
code, answer = %get_reply(sock, 221)
sock:close()
return code, answer
else return nil, err end
end
-----------------------------------------------------------------------------
-- Sends sender command
-- Input
-- sock: server socket
-- sender: e-mail of sender
-- Returns
-- code: server status code, nil if error
-- answer: complete server reply
-----------------------------------------------------------------------------
local send_mail = function(sock, sender)
local param = format("FROM:<%s>", sender)
local err = %send_command(sock, "MAIL", param)
if not err then
return %get_reply(sock, 250)
else return nil, err end
end
-----------------------------------------------------------------------------
-- Sends message mime headers and body
-- Input
-- sock: server socket
-- mime: table containing all mime headers to be sent
-- body: message body
-- Returns
-- code: server status code, nil if error
-- answer: complete server reply
-----------------------------------------------------------------------------
local send_data = function (sock, mime, body)
local err = %send_command(sock, "DATA")
if not err then
local code, answer = %get_reply(sock, 354)
if not code then return nil, answer end
-- avoid premature end in message body
body = gsub(body or "", "\n%.", "\n%.%.")
-- mark end of message body
body = body .. "\r\n."
err = %send_mime(sock, mime)
if err then return nil, err end
err = %puts(sock, body)
return %get_reply(sock, 250)
else return nil, err end
end
-----------------------------------------------------------------------------
-- Sends recipient list command
-- Input
-- sock: server socket
-- rcpt: lua table with recipient list
-- Returns
-- code: server status code, nil if error
-- answer: complete server reply
-----------------------------------------------------------------------------
local send_rcpt = function(sock, rcpt)
local err, code, answer
if type(rcpt) ~= "table" then rcpt = {rcpt} end
for i = 1, getn(rcpt) do
err = %send_command(sock, "RCPT", format("TO:<%s>", rcpt[i]))
if not err then
code, answer = %get_reply(sock, {250, 251})
if not code then return code, answer end
else return nil, err end
end
return code, answer
end
-----------------------------------------------------------------------------
-- Sends verify recipient command
-- Input
-- sock: server socket
-- user: user to be verified
-- Returns
-- code: server status code, nil if error
-- answer: complete server reply
-----------------------------------------------------------------------------
local send_vrfy = function (sock, user)
local err = %send_command(sock, "VRFY", format("<%s>", user))
if not err then
return %get_reply(sock, {250, 251})
else return nil, err end
end
-----------------------------------------------------------------------------
-- Connection oriented mail functions
-----------------------------------------------------------------------------
function smtp_connect(server)
local code, answer
-- connect to server
local sock, err = connect(server, %PORT)
if not sock then return nil, err end
sock:timeout(%TIMEOUT)
-- initial server greeting
code, answer = %get_helo(sock)
if not code then return nil, answer end
-- HELO
code, answer = %send_helo(sock)
if not code then return nil, answer end
return sock
end
function smtp_send(sock, from, rcpt, mime, body)
local code, answer
-- MAIL
code, answer = %send_mail(sock, from)
if not code then return nil, answer end
-- RCPT
code, answer = %send_rcpt(sock, rcpt)
if not code then return nil, answer end
-- DATA
return %send_data(sock, mime, body)
end
function smtp_close(sock)
-- QUIT
return %send_quit(sock)
end
-----------------------------------------------------------------------------
-- Main mail function
-- Input
-- from: message sender
-- rcpt: table containing message recipients
-- mime: table containing mime headers
-- body: message body
-- server: smtp server to be used
-- Returns
-- nil if successfull, error message in case of error
-----------------------------------------------------------------------------
function smtp_mail(from, rcpt, mime, body, server)
local sock, err = smtp_connect(server)
if not sock then return err end
local code, answer = smtp_send(sock, from, rcpt, mime, body)
if not code then return answer end
code, answer = smtp_close(sock)
if not code then return answer
else return nil end
end
--===========================================================================
-- Compatibility functions
--===========================================================================
-----------------------------------------------------------------------------
-- Converts a comma separated list into a Lua table with one entry for each
-- list element.
-- Input
-- str: string containing the list to be converted
-- tab: table to be filled with entries
-- Returns
-- a table t, where t.n is the number of elements with an entry t[i]
-- for each element
-----------------------------------------------------------------------------
local fill = function(str, tab)
gsub(str, "([^%s,]+)", function (w) tinsert(%tab, w) end)
return tab
end
-----------------------------------------------------------------------------
-- Client mail function, implementing CGILUA 3.2 interface
-----------------------------------------------------------------------------
function mail(msg)
local rcpt = {}
local mime = {}
mime["Subject"] = msg.subject
mime["To"] = msg.to
mime["From"] = msg.from
%fill(msg.to, rcpt)
if msg.cc then
%fill(msg.cc, rcpt)
mime["Cc"] = msg.cc
end
if msg.bcc then
%fill(msg.bcc, rcpt)
end
rcpt.n = nil
return %smtp_mail(msg.from, rcpt, mime, msg.message, msg.mailserver)
end