aboutsummaryrefslogtreecommitdiff
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
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).
-rw-r--r--TODO33
-rw-r--r--src/luasocket.c6
-rw-r--r--src/smtp.lua415
3 files changed, 165 insertions, 289 deletions
diff --git a/TODO b/TODO
index 35a5225..ce3ee38 100644
--- a/TODO
+++ b/TODO
@@ -1,3 +1,34 @@
1
2Read about
3
4250-ENHANCEDSTATUSCODES
5250-PIPELINING
6250-8BITMIME
7250-SIZE
8250-DSN
9250-ETRN
10250-AUTH GSSAPI
11250-DELIVERBY
12250 HELP
13
14Change return of send and receive callbacks to allow for
15new functions. "" signals end of transmission. Pass total
16number of bytes in request table for HTTP. Callback has nothing
17to do with it.
18
19Make sure nobody can fuck up with the metatables...
20
21Create a passive mode option for the FTP (good for firewall).
22
23Use environments in module definitions or declare all local and create the
24function with exported symbols later?
25
26local P = {}
27complex = P
28setfenv(1, P)
29
30Modules should return their namespace table in the end of the chunk.
31
1Adjust dates in all files 32Adjust dates in all files
2 33
3Test the library on every system possible 34Test the library on every system possible
@@ -7,7 +38,7 @@ Document socket.time and socket.sleep
7Implement time critical stuff from code module in C. 38Implement time critical stuff from code module in C.
8Add service name translation. 39Add service name translation.
9 40
10Ajeitar o protocolo da lua_socketlibopen()... 41Ajeitar o protocolo da luaopen_socket()... sei lá qual é.
11 42
12- testar os options! 43- testar os options!
13- adicionar exemplos de expansão: pipe, local, named pipe 44- adicionar exemplos de expansão: pipe, local, named pipe
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