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