aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDiego Nehab <diego@tecgraf.puc-rio.br>2003-10-21 01:12:23 +0000
committerDiego Nehab <diego@tecgraf.puc-rio.br>2003-10-21 01:12:23 +0000
commit9bc4e0648ab4a177293a94425594bdc54c9a84fa (patch)
tree21c9be82a75382af48b10a75636fadb49c315456 /src
parent24fbcf34707b79ed3d2f6330ea909dfb42a9648b (diff)
downloadluasocket-9bc4e0648ab4a177293a94425594bdc54c9a84fa.tar.gz
luasocket-9bc4e0648ab4a177293a94425594bdc54c9a84fa.tar.bz2
luasocket-9bc4e0648ab4a177293a94425594bdc54c9a84fa.zip
First version of generic SMTP code and of the base64 encoding.
Gonna rewrite the base64 stuff (found a better way).
Diffstat (limited to 'src')
-rw-r--r--src/luasocket.c6
-rw-r--r--src/smtp.lua415
2 files changed, 133 insertions, 288 deletions
diff --git a/src/luasocket.c b/src/luasocket.c
index 9be5595..8b30f4d 100644
--- a/src/luasocket.c
+++ b/src/luasocket.c
@@ -33,6 +33,7 @@
33#include "tcp.h" 33#include "tcp.h"
34#include "udp.h" 34#include "udp.h"
35#include "select.h" 35#include "select.h"
36#include "code.h"
36 37
37/*=========================================================================*\ 38/*=========================================================================*\
38* Exported functions 39* Exported functions
@@ -51,21 +52,22 @@ LUASOCKET_API int luaopen_socket(lua_State *L)
51 tcp_open(L); 52 tcp_open(L);
52 udp_open(L); 53 udp_open(L);
53 select_open(L); 54 select_open(L);
55 code_open(L);
54#ifdef LUASOCKET_COMPILED 56#ifdef LUASOCKET_COMPILED
55#include "auxiliar.lch" 57#include "auxiliar.lch"
56#include "concat.lch" 58#include "concat.lch"
57#include "code.lch"
58#include "url.lch" 59#include "url.lch"
59#include "callback.lch" 60#include "callback.lch"
61#include "code.lch"
60#include "smtp.lch" 62#include "smtp.lch"
61#include "ftp.lch" 63#include "ftp.lch"
62#include "http.lch" 64#include "http.lch"
63#else 65#else
64 lua_dofile(L, "auxiliar.lua"); 66 lua_dofile(L, "auxiliar.lua");
65 lua_dofile(L, "concat.lua"); 67 lua_dofile(L, "concat.lua");
66 lua_dofile(L, "code.lua");
67 lua_dofile(L, "url.lua"); 68 lua_dofile(L, "url.lua");
68 lua_dofile(L, "callback.lua"); 69 lua_dofile(L, "callback.lua");
70 lua_dofile(L, "code.lua");
69 lua_dofile(L, "smtp.lua"); 71 lua_dofile(L, "smtp.lua");
70 lua_dofile(L, "ftp.lua"); 72 lua_dofile(L, "ftp.lua");
71 lua_dofile(L, "http.lua"); 73 lua_dofile(L, "http.lua");
diff --git a/src/smtp.lua b/src/smtp.lua
index 5249160..25d7f74 100644
--- a/src/smtp.lua
+++ b/src/smtp.lua
@@ -1,314 +1,157 @@
1----------------------------------------------------------------------------- 1-- make sure LuaSocket is loaded
2-- SMTP support for the Lua language. 2if not LUASOCKET_LIBNAME then error('module requires LuaSocket') end
3-- LuaSocket toolkit 3-- get LuaSocket namespace
4-- Author: Diego Nehab 4local socket = _G[LUASOCKET_LIBNAME]
5-- Conforming to: RFC 821, LTN7 5if not socket then error('module requires LuaSocket') end
6-- RCS ID: $Id$ 6-- create smtp namespace inside LuaSocket namespace
7----------------------------------------------------------------------------- 7local smtp = {}
8 8socket.smtp = smtp
9local Public, Private = {}, {} 9-- make all module globals fall into smtp namespace
10local socket = _G[LUASOCKET_LIBNAME] -- get LuaSocket namespace 10setmetatable(smtp, { __index = _G })
11socket.smtp = Public -- create smtp sub namespace 11setfenv(1, smtp)
12 12
13----------------------------------------------------------------------------- 13-- default port
14-- Program constants 14PORT = 25
15-----------------------------------------------------------------------------
16-- timeout in secconds before we give up waiting
17Public.TIMEOUT = 180
18-- port used for connection
19Public.PORT = 25
20-- domain used in HELO command and default sendmail 15-- domain used in HELO command and default sendmail
21-- If we are under a CGI, try to get from environment 16-- If we are under a CGI, try to get from environment
22Public.DOMAIN = os.getenv("SERVER_NAME") or "localhost" 17DOMAIN = os.getenv("SERVER_NAME") or "localhost"
23-- default server used to send e-mails 18-- default server used to send e-mails
24Public.SERVER = "localhost" 19SERVER = "localhost"
25 20
26----------------------------------------------------------------------------- 21-- tries to get a pattern from the server and closes socket on error
27-- Tries to get a pattern from the server and closes socket on error 22local function try_receiving(connection, pattern)
28-- sock: socket connected to the server 23 local data, message = connection:receive(pattern)
29-- pattern: pattern to receive 24 if not data then connection:close() end
30-- Returns 25 print(data)
31-- received pattern on success 26 return data, message
32-- nil followed by error message on error
33-----------------------------------------------------------------------------
34function Private.try_receive(sock, pattern)
35 local data, err = sock:receive(pattern)
36 if not data then sock:close() end
37 return data, err
38end
39
40-----------------------------------------------------------------------------
41-- Tries to send data to the server and closes socket on error
42-- sock: socket connected to the server
43-- data: data to send
44-- Returns
45-- err: error message if any, nil if successfull
46-----------------------------------------------------------------------------
47function Private.try_send(sock, data)
48 local sent, err = sock:send(data)
49 if not sent then sock:close() end
50 return err
51end 27end
52 28
53----------------------------------------------------------------------------- 29-- tries to send data to server and closes socket on error
54-- Sends a command to the server (closes sock on error) 30local function try_sending(connection, data)
55-- Input 31 local sent, message = connection:send(data)
56-- sock: server socket 32 if not sent then connection:close() end
57-- command: command to be sent 33 io.write(data)
58-- param: command parameters if any 34 return sent, message
59-- Returns
60-- err: error message if any
61-----------------------------------------------------------------------------
62function Private.send_command(sock, command, param)
63 local line
64 if param then line = command .. " " .. param .. "\r\n"
65 else line = command .. "\r\n" end
66 return Private.try_send(sock, line)
67end 35end
68 36
69----------------------------------------------------------------------------- 37-- gets server reply
70-- Gets command reply, (accepts multiple-line replies) 38local function get_reply(connection)
71-- Input 39 local code, current, separator, _
72-- control: control openion socket 40 local line, message = try_receiving(connection)
73-- Returns 41 local reply = line
74-- answer: whole server reply, nil if error 42 if message then return nil, message end
75-- code: reply status code or error message 43 _, _, code, separator = string.find(line, "^(%d%d%d)(.?)")
76----------------------------------------------------------------------------- 44 if not code then return nil, "invalid server reply" end
77function Private.get_answer(control) 45 if separator == "-" then -- reply is multiline
78 local code, lastcode, sep, _ 46 repeat
79 local line, err = Private.try_receive(control) 47 line, message = try_receiving(connection)
80 local answer = line 48 if message then return nil, message end
81 if err then return nil, err end 49 _,_, current, separator = string.find(line, "^(%d%d%d)(.)")
82 _,_, code, sep = string.find(line, "^(%d%d%d)(.)") 50 if not current or not separator then
83 if not code or not sep then return nil, answer end 51 return nil, "invalid server reply"
84 if sep == "-" then -- answer is multiline 52 end
85 repeat 53 reply = reply .. "\n" .. line
86 line, err = Private.try_receive(control) 54 -- reply ends with same code
87 if err then return nil, err end 55 until code == current and separator == " "
88 _,_, lastcode, sep = string.find(line, "^(%d%d%d)(.)")
89 answer = answer .. "\n" .. line
90 until code == lastcode and sep == " " -- answer ends with same code
91 end 56 end
92 return answer, tonumber(code) 57 return code, reply
93end 58end
94 59
95----------------------------------------------------------------------------- 60-- metatable for server connection object
96-- Checks if a message reply code is correct. Closes control openion 61local metatable = { __index = {} }
97-- if not. 62
98-- Input 63-- switch handler for execute function
99-- control: control openion socket 64local switch = {}
100-- success: table with successfull reply status code 65
101-- Returns 66-- execute the "check" instruction
102-- code: reply code or nil in case of error 67function switch.check(connection, instruction)
103-- answer: complete server answer or system error message 68 local code, reply = get_reply(connection)
104----------------------------------------------------------------------------- 69 if not code then return nil, reply end
105function Private.check_answer(control, success) 70 if type(instruction.check) == "function" then
106 local answer, code = Private.get_answer(control) 71 return instruction.check(code, reply)
107 if not answer then return nil, code end 72 else
108 if type(success) ~= "table" then success = {success} end 73 if string.find(code, instruction.check) then return code, reply
109 for i = 1, table.getn(success) do 74 else return nil, reply end
110 if code == success[i] then
111 return code, answer
112 end
113 end 75 end
114 control:close()
115 return nil, answer
116end 76end
117 77
118----------------------------------------------------------------------------- 78-- stub for invalid instructions
119-- Sends initial client greeting 79function switch.invalid(connection, instruction)
120-- Input 80 return nil, "invalid instruction"
121-- sock: server socket
122-- Returns
123-- code: server code if ok, nil if error
124-- answer: complete server reply
125-----------------------------------------------------------------------------
126function Private.send_helo(sock)
127 local err = Private.send_command(sock, "HELO", Public.DOMAIN)
128 if err then return nil, err end
129 return Private.check_answer(sock, 250)
130end 81end
131 82
132----------------------------------------------------------------------------- 83-- execute the "command" instruction
133-- Sends openion termination command 84function switch.command(connection, instruction)
134-- Input 85 local line
135-- sock: server socket 86 if instruction.argument then
136-- Returns 87 line = instruction.command .. " " .. instruction.argument .. "\r\n"
137-- code: server status code, nil if error 88 else line = instruction.command .. "\r\n" end
138-- answer: complete server reply or error message 89 return try_sending(connection, line)
139-----------------------------------------------------------------------------
140function Private.send_quit(sock)
141 local err = Private.send_command(sock, "QUIT")
142 if err then return nil, err end
143 local code, answer = Private.check_answer(sock, 221)
144 sock:close()
145 return code, answer
146end
147
148-----------------------------------------------------------------------------
149-- Sends sender command
150-- Input
151-- sock: server socket
152-- sender: e-mail of sender
153-- Returns
154-- code: server status code, nil if error
155-- answer: complete server reply or error message
156-----------------------------------------------------------------------------
157function Private.send_mail(sock, sender)
158 local param = string.format("FROM:<%s>", sender or "")
159 local err = Private.send_command(sock, "MAIL", param)
160 if err then return nil, err end
161 return Private.check_answer(sock, 250)
162end 90end
163 91
164----------------------------------------------------------------------------- 92function switch.raw(connection, instruction)
165-- Sends mime headers 93 if type(instruction.raw) == "function" then
166-- Input 94 local f = instruction.raw
167-- sock: server socket 95 while true do
168-- headers: table with mime headers to be sent 96 local chunk, new_f = f()
169-- Returns 97 if not chunk then return nil, new_f end
170-- err: error message if any 98 if chunk == "" then return true end
171----------------------------------------------------------------------------- 99 f = new_f or f
172function Private.send_headers(sock, headers) 100 local code, message = try_sending(connection, chunk)
173 local err 101 if not code then return nil, message end
174 -- send request headers 102 end
175 for i, v in headers or {} do 103 else return try_sending(connection, instruction.raw) end
176 err = Private.try_send(sock, i .. ": " .. v .. "\r\n")
177 if err then return err end
178 end
179 -- mark end of request headers
180 return Private.try_send(sock, "\r\n")
181end 104end
182 105
183----------------------------------------------------------------------------- 106-- finds out what instruction are we dealing with
184-- Sends message mime headers and body 107local function instruction_type(instruction)
185-- Input 108 if type(instruction) ~= "table" then return "invalid" end
186-- sock: server socket 109 if instruction.command then return "command" end
187-- headers: table containing all mime headers to be sent 110 if instruction.check then return "check" end
188-- body: message body 111 if instruction.raw then return "raw" end
189-- Returns 112 return "invalid"
190-- code: server status code, nil if error
191-- answer: complete server reply or error message
192-----------------------------------------------------------------------------
193function Private.send_data(sock, headers, body)
194 local err = Private.send_command(sock, "DATA")
195 if err then return nil, err end
196 local code, answer = Private.check_answer(sock, 354)
197 if not code then return nil, answer end
198 -- avoid premature end in message body
199 body = string.gsub(body or "", "\n%.", "\n%.%.")
200 -- mark end of message body
201 body = body .. "\r\n.\r\n"
202 err = Private.send_headers(sock, headers)
203 if err then return nil, err end
204 err = Private.try_send(sock, body)
205 return Private.check_answer(sock, 250)
206end 113end
207 114
208----------------------------------------------------------------------------- 115-- execute a list of instructions
209-- Sends recipient list command 116function metatable.__index:execute(instructions)
210-- Input 117 if type(instructions) ~= "table" then error("instruction expected", 1) end
211-- sock: server socket 118 if not instructions[1] then instructions = { instructions } end
212-- rcpt: lua table with recipient list 119 local code, message
213-- Returns 120 for _, instruction in ipairs(instructions) do
214-- code: server status code, nil if error 121 local type = instruction_type(instruction)
215-- answer: complete server reply 122 code, message = switch[type](self.connection, instruction)
216----------------------------------------------------------------------------- 123 if not code then break end
217function Private.send_rcpt(sock, rcpt)
218 local err
219 local code, answer = nil, "No recipient specified"
220 if type(rcpt) ~= "table" then rcpt = {rcpt} end
221 for i = 1, table.getn(rcpt) do
222 err = Private.send_command(sock, "RCPT",
223 string.format("TO:<%s>", rcpt[i]))
224 if err then return nil, err end
225 code, answer = Private.check_answer(sock, {250, 251})
226 if not code then return code, answer end
227 end 124 end
228 return code, answer 125 return code, message
229end 126end
230 127
231----------------------------------------------------------------------------- 128-- closes the underlying connection
232-- Starts the connection and greets server 129function metatable.__index:close()
233-- Input 130 self.connection:close()
234-- parsed: parsed URL components
235-- Returns
236-- sock: socket connected to server
237-- err: error message if any
238-----------------------------------------------------------------------------
239function Private.open(server)
240 local code, answer
241 -- default server
242 server = server or Public.SERVER
243 -- connect to server and make sure we won't hang
244 local sock, err = socket.connect(server, Public.PORT)
245 if not sock then return nil, err end
246 sock:settimeout(Public.TIMEOUT)
247 -- initial server greeting
248 code, answer = Private.check_answer(sock, 220)
249 if not code then return nil, answer end
250 -- HELO
251 code, answer = Private.send_helo(sock)
252 if not code then return nil, answer end
253 return sock
254end 131end
255 132
256----------------------------------------------------------------------------- 133-- connect with server and return a smtp connection object
257-- Sends a message using an opened server 134function connect(host)
258-- Input 135 local connection, message = socket.connect(host, PORT)
259-- sock: socket connected to server 136 if not connection then return nil, message end
260-- message: a table with the following fields: 137 return setmetatable({ connection = connection }, metatable)
261-- from: message sender's e-mail
262-- rcpt: message recipient's e-mail
263-- headers: message mime headers
264-- body: messge body
265-- Returns
266-- code: server status code, nil if error
267-- answer: complete server reply
268-----------------------------------------------------------------------------
269function Private.send(sock, message)
270 local code, answer
271 -- MAIL
272 code, answer = Private.send_mail(sock, message.from)
273 if not code then return nil, answer end
274 -- RCPT
275 code, answer = Private.send_rcpt(sock, message.rcpt)
276 if not code then return nil, answer end
277 -- DATA
278 return Private.send_data(sock, message.headers, message.body)
279end 138end
280 139
281----------------------------------------------------------------------------- 140-- simple test drive
282-- Closes connection with server 141
283-- Input 142--[[
284-- sock: socket connected to server 143c, m = connect("localhost")
285-- Returns 144assert(c, m)
286-- code: server status code, nil if error 145assert(c:execute {check = "2.." })
287-- answer: complete server reply 146assert(c:execute {{command = "EHLO", argument = "localhost"}, {check = "2.."}})
288----------------------------------------------------------------------------- 147assert(c:execute {command = "MAIL", argument = "FROM:<diego@princeton.edu>"})
289function Private.close(sock) 148assert(c:execute {check = "2.."})
290 -- QUIT 149assert(c:execute {command = "RCPT", argument = "TO:<diego@cs.princeton.edu>"})
291 return Private.send_quit(sock) 150assert(c:execute {check = function (code) return code == "250" end})
292end 151assert(c:execute {{command = "DATA"}, {check = "3.."}})
293 152assert(c:execute {{raw = "This is the message\r\n.\r\n"}, {check = "2.."}})
294----------------------------------------------------------------------------- 153assert(c:execute {{command = "QUIT"}, {check = "2.."}})
295-- Main mail function 154c:close()
296-- Input 155]]
297-- message: a table with the following fields: 156
298-- from: message sender 157return smtp
299-- rcpt: table containing message recipients
300-- headers: table containing mime headers
301-- body: message body
302-- server: smtp server to be used
303-- Returns
304-- nil if successfull, error message in case of error
305-----------------------------------------------------------------------------
306function Public.mail(message)
307 local sock, err = Private.open(message.server)
308 if not sock then return nil, err end
309 local code, answer = Private.send(sock, message)
310 if not code then return nil, answer end
311 code, answer = Private.close(sock)
312 if code then return 1
313 else return nil, answer end
314end