aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDiego Nehab <diego@tecgraf.puc-rio.br>2001-09-12 18:16:31 +0000
committerDiego Nehab <diego@tecgraf.puc-rio.br>2001-09-12 18:16:31 +0000
commit480689a70217d2ec9bedf699166f05bb972e9448 (patch)
treea4baebdd32ad0eeecf0f98ce4e6e97e5269612ae /src
parent504ecdc0aaeb89dd16cfc8f058de3b03d869270e (diff)
downloadluasocket-480689a70217d2ec9bedf699166f05bb972e9448.tar.gz
luasocket-480689a70217d2ec9bedf699166f05bb972e9448.tar.bz2
luasocket-480689a70217d2ec9bedf699166f05bb972e9448.zip
Updated for LuaSocket 1.4, following LTN7 etc.
Module is now automaticaly tested.
Diffstat (limited to 'src')
-rw-r--r--src/smtp.lua303
1 files changed, 153 insertions, 150 deletions
diff --git a/src/smtp.lua b/src/smtp.lua
index 6404e6c..7450792 100644
--- a/src/smtp.lua
+++ b/src/smtp.lua
@@ -1,56 +1,91 @@
1----------------------------------------------------------------------------- 1-----------------------------------------------------------------------------
2-- Simple SMTP support for the Lua language using the LuaSocket toolkit. 2-- SMTP support for the Lua language.
3-- LuaSocket 1.4 toolkit
3-- Author: Diego Nehab 4-- Author: Diego Nehab
4-- Date: 26/12/2000 5-- Date: 26/12/2000
5-- Conforming to: RFC 821 6-- Conforming to: RFC 821, LTN7
7-- RCS ID: $Id$
6----------------------------------------------------------------------------- 8-----------------------------------------------------------------------------
7 9
10local Public, Private = {}, {}
11SMTP = Public
12
8----------------------------------------------------------------------------- 13-----------------------------------------------------------------------------
9-- Program constants 14-- Program constants
10----------------------------------------------------------------------------- 15-----------------------------------------------------------------------------
11-- timeout in secconds before we give up waiting 16-- timeout in secconds before we give up waiting
12local TIMEOUT = 180 17Public.TIMEOUT = 180
13-- port used for connection 18-- port used for connection
14local PORT = 25 19Public.PORT = 25
15-- domain used in HELO command. If we are under a CGI, try to get from 20-- domain used in HELO command and default sendmail
16-- environment 21-- If we are under a CGI, try to get from environment
17local DOMAIN = getenv("SERVER_NAME") 22Public.DOMAIN = getenv("SERVER_NAME") or "localhost"
18if not DOMAIN then 23-- default server used to send e-mails
19 DOMAIN = "localhost" 24Public.SERVER = "localhost"
20end
21 25
22----------------------------------------------------------------------------- 26-----------------------------------------------------------------------------
23-- Tries to send DOS mode lines. Closes socket on error. 27-- Tries to send data through socket. Closes socket on error.
24-- Input 28-- Input
25-- sock: server socket 29-- sock: server socket
26-- line: string to be sent 30-- data: string to be sent
27-- Returns 31-- Returns
28-- err: message in case of error, nil if successfull 32-- err: message in case of error, nil if successfull
29----------------------------------------------------------------------------- 33-----------------------------------------------------------------------------
30local try_send = function(sock, line) 34function Private.try_send(sock, data)
31 local err = sock:send(line .. "\r\n") 35 local err = sock:send(data)
32 if err then sock:close() end 36 if err then sock:close() end
33 return err 37 return err
34end 38end
35 39
36----------------------------------------------------------------------------- 40-----------------------------------------------------------------------------
41-- Tries to get a pattern from the server and closes socket on error
42-- sock: socket opened to the server
43-- ...: pattern to receive
44-- Returns
45-- ...: received pattern
46-- err: error message if any
47-----------------------------------------------------------------------------
48function Private.try_receive(...)
49 local sock = arg[1]
50 local data, err = call(sock.receive, arg)
51 if err then sock:close() end
52 return data, err
53end
54
55-----------------------------------------------------------------------------
56-- Sends a command to the server (closes sock on error)
57-- Input
58-- sock: server socket
59-- command: command to be sent
60-- param: command parameters if any
61-- Returns
62-- err: error message if any
63-----------------------------------------------------------------------------
64function Private.send_command(sock, command, param)
65 local line
66 if param then line = command .. " " .. param .. "\r\n"
67 else line = command .. "\r\n" end
68 return %Private.try_send(sock, line)
69end
70
71-----------------------------------------------------------------------------
37-- Gets command reply, (accepts multiple-line replies) 72-- Gets command reply, (accepts multiple-line replies)
38-- Input 73-- Input
39-- control: control connection socket 74-- control: control openion socket
40-- Returns 75-- Returns
41-- answer: whole server reply, nil if error 76-- answer: whole server reply, nil if error
42-- code: reply status code or error message 77-- code: reply status code or error message
43----------------------------------------------------------------------------- 78-----------------------------------------------------------------------------
44local get_answer = function(control) 79function Private.get_answer(control)
45 local code, lastcode, sep 80 local code, lastcode, sep, _
46 local line, err = control:receive() 81 local line, err = %Private.try_receive(control)
47 local answer = line 82 local answer = line
48 if err then return nil, err end 83 if err then return nil, err end
49 _,_, code, sep = strfind(line, "^(%d%d%d)(.)") 84 _,_, code, sep = strfind(line, "^(%d%d%d)(.)")
50 if not code or not sep then return nil, answer end 85 if not code or not sep then return nil, answer end
51 if sep == "-" then -- answer is multiline 86 if sep == "-" then -- answer is multiline
52 repeat 87 repeat
53 line, err = control:receive() 88 line, err = %Private.try_receive(control)
54 if err then return nil, err end 89 if err then return nil, err end
55 _,_, lastcode, sep = strfind(line, "^(%d%d%d)(.)") 90 _,_, lastcode, sep = strfind(line, "^(%d%d%d)(.)")
56 answer = answer .. "\n" .. line 91 answer = answer .. "\n" .. line
@@ -60,21 +95,18 @@ local get_answer = function(control)
60end 95end
61 96
62----------------------------------------------------------------------------- 97-----------------------------------------------------------------------------
63-- Checks if a message reply code is correct. Closes control connection 98-- Checks if a message reply code is correct. Closes control openion
64-- if not. 99-- if not.
65-- Input 100-- Input
66-- control: control connection socket 101-- control: control openion socket
67-- success: table with successfull reply status code 102-- success: table with successfull reply status code
68-- Returns 103-- Returns
69-- code: reply code or nil in case of error 104-- code: reply code or nil in case of error
70-- answer: complete server answer or system error message 105-- answer: complete server answer or system error message
71----------------------------------------------------------------------------- 106-----------------------------------------------------------------------------
72local check_answer = function(control, success) 107function Private.check_answer(control, success)
73 local answer, code = %get_answer(control) 108 local answer, code = %Private.get_answer(control)
74 if not answer then 109 if not answer then return nil, code end
75 control:close()
76 return nil, code
77 end
78 if type(success) ~= "table" then success = {success} end 110 if type(success) ~= "table" then success = {success} end
79 for i = 1, getn(success) do 111 for i = 1, getn(success) do
80 if code == success[i] then 112 if code == success[i] then
@@ -86,22 +118,6 @@ local check_answer = function(control, success)
86end 118end
87 119
88----------------------------------------------------------------------------- 120-----------------------------------------------------------------------------
89-- Sends a command to the server (closes sock on error)
90-- Input
91-- sock: server socket
92-- command: command to be sent
93-- param: command parameters if any
94-- Returns
95-- err: error message if any
96-----------------------------------------------------------------------------
97local send_command = function(sock, command, param)
98 local line
99 if param then line = command .. " " .. param
100 else line = command end
101 return %try_send(sock, line)
102end
103
104-----------------------------------------------------------------------------
105-- Sends initial client greeting 121-- Sends initial client greeting
106-- Input 122-- Input
107-- sock: server socket 123-- sock: server socket
@@ -109,49 +125,24 @@ end
109-- code: server code if ok, nil if error 125-- code: server code if ok, nil if error
110-- answer: complete server reply 126-- answer: complete server reply
111----------------------------------------------------------------------------- 127-----------------------------------------------------------------------------
112local send_helo = function(sock) 128function Private.send_helo(sock)
113 local err = %send_command(sock, "HELO", %DOMAIN) 129 local err = %Private.send_command(sock, "HELO", %Public.DOMAIN)
114 if err then return nil, err end 130 if err then return nil, err end
115 return %check_answer(sock, 250) 131 return %Private.check_answer(sock, 250)
116end
117
118-----------------------------------------------------------------------------
119-- Sends mime headers
120-- Input
121-- sock: server socket
122-- mime: table with mime headers to be sent
123-- Returns
124-- err: error message if any
125-----------------------------------------------------------------------------
126local send_mime = function(sock, mime)
127 local err
128 mime = mime or {}
129 -- send all headers
130 for name,value in mime do
131 err = sock:send(name .. ": " .. value .. "\r\n")
132 if err then
133 sock:close()
134 return err
135 end
136 end
137 -- end mime part
138 err = sock:send("\r\n")
139 if err then sock:close() end
140 return err
141end 132end
142 133
143----------------------------------------------------------------------------- 134-----------------------------------------------------------------------------
144-- Sends connection termination command 135-- Sends openion termination command
145-- Input 136-- Input
146-- sock: server socket 137-- sock: server socket
147-- Returns 138-- Returns
148-- code: server status code, nil if error 139-- code: server status code, nil if error
149-- answer: complete server reply or error message 140-- answer: complete server reply or error message
150----------------------------------------------------------------------------- 141-----------------------------------------------------------------------------
151local send_quit = function(sock) 142function Private.send_quit(sock)
152 local err = %send_command(sock, "QUIT") 143 local err = %Private.send_command(sock, "QUIT")
153 if err then return nil, err end 144 if err then return nil, err end
154 local code, answer = %check_answer(sock, 221) 145 local code, answer = %Private.check_answer(sock, 221)
155 sock:close() 146 sock:close()
156 return code, answer 147 return code, answer
157end 148end
@@ -165,36 +156,55 @@ end
165-- code: server status code, nil if error 156-- code: server status code, nil if error
166-- answer: complete server reply or error message 157-- answer: complete server reply or error message
167----------------------------------------------------------------------------- 158-----------------------------------------------------------------------------
168local send_mail = function(sock, sender) 159function Private.send_mail(sock, sender)
169 local param = format("FROM:<%s>", sender) 160 local param = format("FROM:<%s>", sender or "")
170 local err = %send_command(sock, "MAIL", param) 161 local err = %Private.send_command(sock, "MAIL", param)
171 if err then return nil, err end 162 if err then return nil, err end
172 return %check_answer(sock, 250) 163 return %Private.check_answer(sock, 250)
164end
165
166-----------------------------------------------------------------------------
167-- Sends mime headers
168-- Input
169-- sock: server socket
170-- headers: table with mime headers to be sent
171-- Returns
172-- err: error message if any
173-----------------------------------------------------------------------------
174function Private.send_headers(sock, headers)
175 local err
176 -- send request headers
177 for i, v in headers or {} do
178 err = %Private.try_send(sock, i .. ": " .. v .. "\r\n")
179 if err then return err end
180 end
181 -- mark end of request headers
182 return %Private.try_send(sock, "\r\n")
173end 183end
174 184
175----------------------------------------------------------------------------- 185-----------------------------------------------------------------------------
176-- Sends message mime headers and body 186-- Sends message mime headers and body
177-- Input 187-- Input
178-- sock: server socket 188-- sock: server socket
179-- mime: table containing all mime headers to be sent 189-- headers: table containing all mime headers to be sent
180-- body: message body 190-- body: message body
181-- Returns 191-- Returns
182-- code: server status code, nil if error 192-- code: server status code, nil if error
183-- answer: complete server reply or error message 193-- answer: complete server reply or error message
184----------------------------------------------------------------------------- 194-----------------------------------------------------------------------------
185local send_data = function (sock, mime, body) 195function Private.send_data(sock, headers, body)
186 local err = %send_command(sock, "DATA") 196 local err = %Private.send_command(sock, "DATA")
187 if err then return nil, err end 197 if err then return nil, err end
188 local code, answer = %check_answer(sock, 354) 198 local code, answer = %Private.check_answer(sock, 354)
189 if not code then return nil, answer end 199 if not code then return nil, answer end
190 -- avoid premature end in message body 200 -- avoid premature end in message body
191 body = gsub(body or "", "\n%.", "\n%.%.") 201 body = gsub(body or "", "\n%.", "\n%.%.")
192 -- mark end of message body 202 -- mark end of message body
193 body = body .. "\r\n." 203 body = body .. "\r\n.\r\n"
194 err = %send_mime(sock, mime) 204 err = %Private.send_headers(sock, headers)
195 if err then return nil, err end 205 if err then return nil, err end
196 err = %try_send(sock, body) 206 err = %Private.try_send(sock, body)
197 return %check_answer(sock, 250) 207 return %Private.check_answer(sock, 250)
198end 208end
199 209
200----------------------------------------------------------------------------- 210-----------------------------------------------------------------------------
@@ -206,107 +216,100 @@ end
206-- code: server status code, nil if error 216-- code: server status code, nil if error
207-- answer: complete server reply 217-- answer: complete server reply
208----------------------------------------------------------------------------- 218-----------------------------------------------------------------------------
209local send_rcpt = function(sock, rcpt) 219function Private.send_rcpt(sock, rcpt)
210 local err, code, answer 220 local err
221 local code, answer = nil, "No recipient specified"
211 if type(rcpt) ~= "table" then rcpt = {rcpt} end 222 if type(rcpt) ~= "table" then rcpt = {rcpt} end
212 for i = 1, getn(rcpt) do 223 for i = 1, getn(rcpt) do
213 err = %send_command(sock, "RCPT", format("TO:<%s>", rcpt[i])) 224 err = %Private.send_command(sock, "RCPT", format("TO:<%s>", rcpt[i]))
214 if err then return nil, err end 225 if err then return nil, err end
215 code, answer = %check_answer(sock, {250, 251}) 226 code, answer = %Private.check_answer(sock, {250, 251})
216 if not code then return code, answer end 227 if not code then return code, answer end
217 end 228 end
218 return code, answer 229 return code, answer
219end 230end
220 231
221----------------------------------------------------------------------------- 232-----------------------------------------------------------------------------
222-- Connection oriented mail functions 233-- Starts the connection and greets server
234-- Input
235-- parsed: parsed URL components
236-- Returns
237-- sock: socket connected to server
238-- err: error message if any
223----------------------------------------------------------------------------- 239-----------------------------------------------------------------------------
224function smtp_connect(server) 240function Private.open(server)
225 local code, answer 241 local code, answer
226 -- connect to server 242 -- default server
227 local sock, err = connect(server, %PORT) 243 server = server or %Public.SERVER
244 -- connect to server and make sure we won't hang
245 local sock, err = connect(server, %Public.PORT)
228 if not sock then return nil, err end 246 if not sock then return nil, err end
229 sock:timeout(%TIMEOUT) 247 sock:timeout(%Public.TIMEOUT)
230 -- initial server greeting 248 -- initial server greeting
231 code, answer = %check_answer(sock, 220) 249 code, answer = %Private.check_answer(sock, 220)
232 if not code then return nil, answer end 250 if not code then return nil, answer end
233 -- HELO 251 -- HELO
234 code, answer = %send_helo(sock) 252 code, answer = %Private.send_helo(sock)
235 if not code then return nil, answer end 253 if not code then return nil, answer end
236 return sock 254 return sock
237end 255end
238 256
239function smtp_send(sock, from, rcpt, mime, body) 257-----------------------------------------------------------------------------
258-- Sends a message using an opened server
259-- Input
260-- sock: socket connected to server
261-- message: a table with the following fields:
262-- from: message sender's e-mail
263-- rcpt: message recipient's e-mail
264-- headers: message mime headers
265-- body: messge body
266-- Returns
267-- code: server status code, nil if error
268-- answer: complete server reply
269-----------------------------------------------------------------------------
270function Private.send(sock, message)
240 local code, answer 271 local code, answer
241 -- MAIL 272 -- MAIL
242 code, answer = %send_mail(sock, from) 273 code, answer = %Private.send_mail(sock, message.from)
243 if not code then return nil, answer end 274 if not code then return nil, answer end
244 -- RCPT 275 -- RCPT
245 code, answer = %send_rcpt(sock, rcpt) 276 code, answer = %Private.send_rcpt(sock, message.rcpt)
246 if not code then return nil, answer end 277 if not code then return nil, answer end
247 -- DATA 278 -- DATA
248 return %send_data(sock, mime, body) 279 return %Private.send_data(sock, message.headers, message.body)
249end 280end
250 281
251function smtp_close(sock) 282-----------------------------------------------------------------------------
283-- Closes connection with server
284-- Input
285-- sock: socket connected to server
286-- Returns
287-- code: server status code, nil if error
288-- answer: complete server reply
289-----------------------------------------------------------------------------
290function Private.close(sock)
252 -- QUIT 291 -- QUIT
253 return %send_quit(sock) 292 return %Private.send_quit(sock)
254end 293end
255 294
256----------------------------------------------------------------------------- 295-----------------------------------------------------------------------------
257-- Main mail function 296-- Main mail function
258-- Input 297-- Input
259-- from: message sender 298-- message: a table with the following fields:
260-- rcpt: table containing message recipients 299-- from: message sender
261-- mime: table containing mime headers 300-- rcpt: table containing message recipients
262-- body: message body 301-- headers: table containing mime headers
263-- server: smtp server to be used 302-- body: message body
303-- server: smtp server to be used
264-- Returns 304-- Returns
265-- nil if successfull, error message in case of error 305-- nil if successfull, error message in case of error
266----------------------------------------------------------------------------- 306-----------------------------------------------------------------------------
267function smtp_mail(from, rcpt, mime, body, server) 307function Public.mail(message)
268 local sock, err = smtp_connect(server) 308 local sock, err = %Private.open(message.server)
269 if not sock then return err end 309 if not sock then return err end
270 local code, answer = smtp_send(sock, from, rcpt, mime, body) 310 local code, answer = %Private.send(sock, message)
271 if not code then return answer end 311 if not code then return answer end
272 code, answer = smtp_close(sock) 312 code, answer = %Private.close(sock)
273 if code then return nil end 313 if code then return nil end
274 return answer 314 return answer
275end 315end
276
277--===========================================================================
278-- Compatibility functions
279--===========================================================================
280-----------------------------------------------------------------------------
281-- Converts a comma separated list into a Lua table with one entry for each
282-- list element.
283-- Input
284-- str: string containing the list to be converted
285-- tab: table to be filled with entries
286-- Returns
287-- a table t, where t.n is the number of elements with an entry t[i]
288-- for each element
289-----------------------------------------------------------------------------
290local fill = function(str, tab)
291 gsub(str, "([^%s,]+)", function (w) tinsert(%tab, w) end)
292 return tab
293end
294
295-----------------------------------------------------------------------------
296-- Client mail function, implementing CGILUA 3.2 interface
297-----------------------------------------------------------------------------
298function mail(msg)
299 local rcpt = {}
300 local mime = {}
301 mime["Subject"] = msg.subject
302 mime["To"] = msg.to
303 mime["From"] = msg.from
304 %fill(msg.to, rcpt)
305 if msg.cc then
306 %fill(msg.cc, rcpt)
307 mime["Cc"] = msg.cc
308 end
309 if msg.bcc then %fill(msg.bcc, rcpt) end
310 rcpt.n = nil
311 return %smtp_mail(msg.from, rcpt, mime, msg.message, msg.mailserver)
312end