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
194
195
196
197
198
199
200
201
202
203
204
|
-----------------------------------------------------------------------------
-- SMTP client support for the Lua language.
-- LuaSocket toolkit.
-- Author: Diego Nehab
-- Conforming to RFC 2821
-- RCS ID: $Id$
-----------------------------------------------------------------------------
-- make sure LuaSocket is loaded
require("socket")
-- get LuaSocket namespace
local socket = _G[LUASOCKET_LIBNAME]
require("ltn12")
require("tp")
-- create smtp namespace inside LuaSocket namespace
local smtp = socket.smtp or {}
socket.smtp = smtp
-- make all module globals fall into smtp namespace
setmetatable(smtp, { __index = _G })
setfenv(1, smtp)
-- default server used to send e-mails
SERVER = "localhost"
-- default port
PORT = 25
-- domain used in HELO command and default sendmail
-- If we are under a CGI, try to get from environment
DOMAIN = os.getenv("SERVER_NAME") or "localhost"
-- default time zone (means we don't know)
ZONE = "-0000"
-- high level stuffing filter
function stuff()
return ltn12.filter.cycle(dot, 2)
end
---------------------------------------------------------------------------
-- Low level SMTP API
-----------------------------------------------------------------------------
local metat = { __index = {} }
function metat.__index:greet(domain)
socket.try(self.tp:check("2.."))
socket.try(self.tp:command("EHLO", domain or DOMAIN))
return socket.try(self.tp:check("2.."))
end
function metat.__index:mail(from)
socket.try(self.tp:command("MAIL", "FROM:" .. from))
return socket.try(self.tp:check("2.."))
end
function metat.__index:rcpt(to)
socket.try(self.tp:command("RCPT", "TO:" .. to))
return socket.try(self.tp:check("2.."))
end
function metat.__index:data(src, step)
socket.try(self.tp:command("DATA"))
socket.try(self.tp:check("3.."))
socket.try(self.tp:source(src, step))
socket.try(self.tp:send("\r\n.\r\n"))
return socket.try(self.tp:check("2.."))
end
function metat.__index:quit()
socket.try(self.tp:command("QUIT"))
return socket.try(self.tp:check("2.."))
end
function metat.__index:close()
return socket.try(self.tp:close())
end
-- send message or throw an exception
function metat.__index:send(mailt)
self:mail(mailt.from)
if type(mailt.rcpt) == "table" then
for i,v in ipairs(mailt.rcpt) do
self:rcpt(v)
end
else
self:rcpt(mailt.rcpt)
end
self:data(ltn12.source.chain(mailt.source, stuff()), mailt.step)
end
function open(server, port)
print(server or SERVER, port or PORT)
local tp, error = socket.tp.connect(server or SERVER, port or PORT)
if not tp then return nil, error end
return setmetatable({tp = tp}, metat)
end
---------------------------------------------------------------------------
-- Multipart message source
-----------------------------------------------------------------------------
-- returns a hopefully unique mime boundary
local seqno = 0
local function newboundary()
seqno = seqno + 1
return string.format('%s%05d==%05u', os.date('%d%m%Y%H%M%S'),
math.random(0, 99999), seqno)
end
-- send_message forward declaration
local send_message
-- yield multipart message body from a multipart message table
local function send_multipart(mesgt)
local bd = newboundary()
-- define boundary and finish headers
coroutine.yield('content-type: multipart/mixed; boundary="' ..
bd .. '"\r\n\r\n')
-- send preamble
if mesgt.body.preamble then coroutine.yield(mesgt.body.preamble) end
-- send each part separated by a boundary
for i, m in ipairs(mesgt.body) do
coroutine.yield("\r\n--" .. bd .. "\r\n")
send_message(m)
end
-- send last boundary
coroutine.yield("\r\n--" .. bd .. "--\r\n\r\n")
-- send epilogue
if mesgt.body.epilogue then coroutine.yield(mesgt.body.epilogue) end
end
-- yield message body from a source
local function send_source(mesgt)
-- set content-type if user didn't override
if not mesgt.headers or not mesgt.headers["content-type"] then
coroutine.yield('content-type: text/plain; charset="iso-8859-1"\r\n')
end
-- finish headers
coroutine.yield("\r\n")
-- send body from source
while true do
local chunk, err = mesgt.body()
if err then coroutine.yield(nil, err)
elseif chunk then coroutine.yield(chunk)
else break end
end
end
-- yield message body from a string
local function send_string(mesgt)
-- set content-type if user didn't override
if not mesgt.headers or not mesgt.headers["content-type"] then
coroutine.yield('content-type: text/plain; charset="iso-8859-1"\r\n')
end
-- finish headers
coroutine.yield("\r\n")
-- send body from string
coroutine.yield(mesgt.body)
end
-- yield the headers one by one
local function send_headers(mesgt)
if mesgt.headers then
for i,v in pairs(mesgt.headers) do
coroutine.yield(i .. ':' .. v .. "\r\n")
end
end
end
-- message source
function send_message(mesgt)
send_headers(mesgt)
if type(mesgt.body) == "table" then send_multipart(mesgt)
elseif type(mesgt.body) == "function" then send_source(mesgt)
else send_string(mesgt) end
end
-- set defaul headers
local function adjust_headers(mesgt)
mesgt.headers = mesgt.headers or {}
mesgt.headers["mime-version"] = "1.0"
mesgt.headers["date"] = mesgt.headers["date"] or
os.date("!%a, %d %b %Y %H:%M:%S ") .. (mesgt.zone or ZONE)
mesgt.headers["x-mailer"] = mesgt.headers["x-mailer"] or socket.version
end
function message(mesgt)
adjust_headers(mesgt)
-- create and return message source
local co = coroutine.create(function() send_message(mesgt) end)
return function() return socket.skip(1, coroutine.resume(co)) end
end
---------------------------------------------------------------------------
-- High level SMTP API
-----------------------------------------------------------------------------
send = socket.protect(function(mailt)
local smtp = socket.try(open(mailt.server, mailt.port))
smtp:greet(mailt.domain)
smtp:send(mailt)
smtp:quit()
return smtp:close()
end)
return smtp
|