diff options
Diffstat (limited to 'vendor/luasocket/src/smtp.lua')
-rw-r--r-- | vendor/luasocket/src/smtp.lua | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/vendor/luasocket/src/smtp.lua b/vendor/luasocket/src/smtp.lua new file mode 100644 index 00000000..b113d006 --- /dev/null +++ b/vendor/luasocket/src/smtp.lua | |||
@@ -0,0 +1,256 @@ | |||
1 | ----------------------------------------------------------------------------- | ||
2 | -- SMTP client support for the Lua language. | ||
3 | -- LuaSocket toolkit. | ||
4 | -- Author: Diego Nehab | ||
5 | ----------------------------------------------------------------------------- | ||
6 | |||
7 | ----------------------------------------------------------------------------- | ||
8 | -- Declare module and import dependencies | ||
9 | ----------------------------------------------------------------------------- | ||
10 | local base = _G | ||
11 | local coroutine = require("coroutine") | ||
12 | local string = require("string") | ||
13 | local math = require("math") | ||
14 | local os = require("os") | ||
15 | local socket = require("socket") | ||
16 | local tp = require("socket.tp") | ||
17 | local ltn12 = require("ltn12") | ||
18 | local headers = require("socket.headers") | ||
19 | local mime = require("mime") | ||
20 | |||
21 | socket.smtp = {} | ||
22 | local _M = socket.smtp | ||
23 | |||
24 | ----------------------------------------------------------------------------- | ||
25 | -- Program constants | ||
26 | ----------------------------------------------------------------------------- | ||
27 | -- timeout for connection | ||
28 | _M.TIMEOUT = 60 | ||
29 | -- default server used to send e-mails | ||
30 | _M.SERVER = "localhost" | ||
31 | -- default port | ||
32 | _M.PORT = 25 | ||
33 | -- domain used in HELO command and default sendmail | ||
34 | -- If we are under a CGI, try to get from environment | ||
35 | _M.DOMAIN = os.getenv("SERVER_NAME") or "localhost" | ||
36 | -- default time zone (means we don't know) | ||
37 | _M.ZONE = "-0000" | ||
38 | |||
39 | --------------------------------------------------------------------------- | ||
40 | -- Low level SMTP API | ||
41 | ----------------------------------------------------------------------------- | ||
42 | local metat = { __index = {} } | ||
43 | |||
44 | function metat.__index:greet(domain) | ||
45 | self.try(self.tp:check("2..")) | ||
46 | self.try(self.tp:command("EHLO", domain or _M.DOMAIN)) | ||
47 | return socket.skip(1, self.try(self.tp:check("2.."))) | ||
48 | end | ||
49 | |||
50 | function metat.__index:mail(from) | ||
51 | self.try(self.tp:command("MAIL", "FROM:" .. from)) | ||
52 | return self.try(self.tp:check("2..")) | ||
53 | end | ||
54 | |||
55 | function metat.__index:rcpt(to) | ||
56 | self.try(self.tp:command("RCPT", "TO:" .. to)) | ||
57 | return self.try(self.tp:check("2..")) | ||
58 | end | ||
59 | |||
60 | function metat.__index:data(src, step) | ||
61 | self.try(self.tp:command("DATA")) | ||
62 | self.try(self.tp:check("3..")) | ||
63 | self.try(self.tp:source(src, step)) | ||
64 | self.try(self.tp:send("\r\n.\r\n")) | ||
65 | return self.try(self.tp:check("2..")) | ||
66 | end | ||
67 | |||
68 | function metat.__index:quit() | ||
69 | self.try(self.tp:command("QUIT")) | ||
70 | return self.try(self.tp:check("2..")) | ||
71 | end | ||
72 | |||
73 | function metat.__index:close() | ||
74 | return self.tp:close() | ||
75 | end | ||
76 | |||
77 | function metat.__index:login(user, password) | ||
78 | self.try(self.tp:command("AUTH", "LOGIN")) | ||
79 | self.try(self.tp:check("3..")) | ||
80 | self.try(self.tp:send(mime.b64(user) .. "\r\n")) | ||
81 | self.try(self.tp:check("3..")) | ||
82 | self.try(self.tp:send(mime.b64(password) .. "\r\n")) | ||
83 | return self.try(self.tp:check("2..")) | ||
84 | end | ||
85 | |||
86 | function metat.__index:plain(user, password) | ||
87 | local auth = "PLAIN " .. mime.b64("\0" .. user .. "\0" .. password) | ||
88 | self.try(self.tp:command("AUTH", auth)) | ||
89 | return self.try(self.tp:check("2..")) | ||
90 | end | ||
91 | |||
92 | function metat.__index:auth(user, password, ext) | ||
93 | if not user or not password then return 1 end | ||
94 | if string.find(ext, "AUTH[^\n]+LOGIN") then | ||
95 | return self:login(user, password) | ||
96 | elseif string.find(ext, "AUTH[^\n]+PLAIN") then | ||
97 | return self:plain(user, password) | ||
98 | else | ||
99 | self.try(nil, "authentication not supported") | ||
100 | end | ||
101 | end | ||
102 | |||
103 | -- send message or throw an exception | ||
104 | function metat.__index:send(mailt) | ||
105 | self:mail(mailt.from) | ||
106 | if base.type(mailt.rcpt) == "table" then | ||
107 | for i,v in base.ipairs(mailt.rcpt) do | ||
108 | self:rcpt(v) | ||
109 | end | ||
110 | else | ||
111 | self:rcpt(mailt.rcpt) | ||
112 | end | ||
113 | self:data(ltn12.source.chain(mailt.source, mime.stuff()), mailt.step) | ||
114 | end | ||
115 | |||
116 | function _M.open(server, port, create) | ||
117 | local tp = socket.try(tp.connect(server or _M.SERVER, port or _M.PORT, | ||
118 | _M.TIMEOUT, create)) | ||
119 | local s = base.setmetatable({tp = tp}, metat) | ||
120 | -- make sure tp is closed if we get an exception | ||
121 | s.try = socket.newtry(function() | ||
122 | s:close() | ||
123 | end) | ||
124 | return s | ||
125 | end | ||
126 | |||
127 | -- convert headers to lowercase | ||
128 | local function lower_headers(headers) | ||
129 | local lower = {} | ||
130 | for i,v in base.pairs(headers or lower) do | ||
131 | lower[string.lower(i)] = v | ||
132 | end | ||
133 | return lower | ||
134 | end | ||
135 | |||
136 | --------------------------------------------------------------------------- | ||
137 | -- Multipart message source | ||
138 | ----------------------------------------------------------------------------- | ||
139 | -- returns a hopefully unique mime boundary | ||
140 | local seqno = 0 | ||
141 | local function newboundary() | ||
142 | seqno = seqno + 1 | ||
143 | return string.format('%s%05d==%05u', os.date('%d%m%Y%H%M%S'), | ||
144 | math.random(0, 99999), seqno) | ||
145 | end | ||
146 | |||
147 | -- send_message forward declaration | ||
148 | local send_message | ||
149 | |||
150 | -- yield the headers all at once, it's faster | ||
151 | local function send_headers(tosend) | ||
152 | local canonic = headers.canonic | ||
153 | local h = "\r\n" | ||
154 | for f,v in base.pairs(tosend) do | ||
155 | h = (canonic[f] or f) .. ': ' .. v .. "\r\n" .. h | ||
156 | end | ||
157 | coroutine.yield(h) | ||
158 | end | ||
159 | |||
160 | -- yield multipart message body from a multipart message table | ||
161 | local function send_multipart(mesgt) | ||
162 | -- make sure we have our boundary and send headers | ||
163 | local bd = newboundary() | ||
164 | local headers = lower_headers(mesgt.headers or {}) | ||
165 | headers['content-type'] = headers['content-type'] or 'multipart/mixed' | ||
166 | headers['content-type'] = headers['content-type'] .. | ||
167 | '; boundary="' .. bd .. '"' | ||
168 | send_headers(headers) | ||
169 | -- send preamble | ||
170 | if mesgt.body.preamble then | ||
171 | coroutine.yield(mesgt.body.preamble) | ||
172 | coroutine.yield("\r\n") | ||
173 | end | ||
174 | -- send each part separated by a boundary | ||
175 | for i, m in base.ipairs(mesgt.body) do | ||
176 | coroutine.yield("\r\n--" .. bd .. "\r\n") | ||
177 | send_message(m) | ||
178 | end | ||
179 | -- send last boundary | ||
180 | coroutine.yield("\r\n--" .. bd .. "--\r\n\r\n") | ||
181 | -- send epilogue | ||
182 | if mesgt.body.epilogue then | ||
183 | coroutine.yield(mesgt.body.epilogue) | ||
184 | coroutine.yield("\r\n") | ||
185 | end | ||
186 | end | ||
187 | |||
188 | -- yield message body from a source | ||
189 | local function send_source(mesgt) | ||
190 | -- make sure we have a content-type | ||
191 | local headers = lower_headers(mesgt.headers or {}) | ||
192 | headers['content-type'] = headers['content-type'] or | ||
193 | 'text/plain; charset="iso-8859-1"' | ||
194 | send_headers(headers) | ||
195 | -- send body from source | ||
196 | while true do | ||
197 | local chunk, err = mesgt.body() | ||
198 | if err then coroutine.yield(nil, err) | ||
199 | elseif chunk then coroutine.yield(chunk) | ||
200 | else break end | ||
201 | end | ||
202 | end | ||
203 | |||
204 | -- yield message body from a string | ||
205 | local function send_string(mesgt) | ||
206 | -- make sure we have a content-type | ||
207 | local headers = lower_headers(mesgt.headers or {}) | ||
208 | headers['content-type'] = headers['content-type'] or | ||
209 | 'text/plain; charset="iso-8859-1"' | ||
210 | send_headers(headers) | ||
211 | -- send body from string | ||
212 | coroutine.yield(mesgt.body) | ||
213 | end | ||
214 | |||
215 | -- message source | ||
216 | function send_message(mesgt) | ||
217 | if base.type(mesgt.body) == "table" then send_multipart(mesgt) | ||
218 | elseif base.type(mesgt.body) == "function" then send_source(mesgt) | ||
219 | else send_string(mesgt) end | ||
220 | end | ||
221 | |||
222 | -- set defaul headers | ||
223 | local function adjust_headers(mesgt) | ||
224 | local lower = lower_headers(mesgt.headers) | ||
225 | lower["date"] = lower["date"] or | ||
226 | os.date("!%a, %d %b %Y %H:%M:%S ") .. (mesgt.zone or _M.ZONE) | ||
227 | lower["x-mailer"] = lower["x-mailer"] or socket._VERSION | ||
228 | -- this can't be overriden | ||
229 | lower["mime-version"] = "1.0" | ||
230 | return lower | ||
231 | end | ||
232 | |||
233 | function _M.message(mesgt) | ||
234 | mesgt.headers = adjust_headers(mesgt) | ||
235 | -- create and return message source | ||
236 | local co = coroutine.create(function() send_message(mesgt) end) | ||
237 | return function() | ||
238 | local ret, a, b = coroutine.resume(co) | ||
239 | if ret then return a, b | ||
240 | else return nil, a end | ||
241 | end | ||
242 | end | ||
243 | |||
244 | --------------------------------------------------------------------------- | ||
245 | -- High level SMTP API | ||
246 | ----------------------------------------------------------------------------- | ||
247 | _M.send = socket.protect(function(mailt) | ||
248 | local s = _M.open(mailt.server, mailt.port, mailt.create) | ||
249 | local ext = s:greet(mailt.domain) | ||
250 | s:auth(mailt.user, mailt.password, ext) | ||
251 | s:send(mailt) | ||
252 | s:quit() | ||
253 | return s:close() | ||
254 | end) | ||
255 | |||
256 | return _M \ No newline at end of file | ||