diff options
author | Diego Nehab <diego@tecgraf.puc-rio.br> | 2003-10-21 01:12:23 +0000 |
---|---|---|
committer | Diego Nehab <diego@tecgraf.puc-rio.br> | 2003-10-21 01:12:23 +0000 |
commit | 9bc4e0648ab4a177293a94425594bdc54c9a84fa (patch) | |
tree | 21c9be82a75382af48b10a75636fadb49c315456 | |
parent | 24fbcf34707b79ed3d2f6330ea909dfb42a9648b (diff) | |
download | luasocket-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-- | TODO | 33 | ||||
-rw-r--r-- | src/luasocket.c | 6 | ||||
-rw-r--r-- | src/smtp.lua | 415 |
3 files changed, 165 insertions, 289 deletions
@@ -1,3 +1,34 @@ | |||
1 | |||
2 | Read about | ||
3 | |||
4 | 250-ENHANCEDSTATUSCODES | ||
5 | 250-PIPELINING | ||
6 | 250-8BITMIME | ||
7 | 250-SIZE | ||
8 | 250-DSN | ||
9 | 250-ETRN | ||
10 | 250-AUTH GSSAPI | ||
11 | 250-DELIVERBY | ||
12 | 250 HELP | ||
13 | |||
14 | Change return of send and receive callbacks to allow for | ||
15 | new functions. "" signals end of transmission. Pass total | ||
16 | number of bytes in request table for HTTP. Callback has nothing | ||
17 | to do with it. | ||
18 | |||
19 | Make sure nobody can fuck up with the metatables... | ||
20 | |||
21 | Create a passive mode option for the FTP (good for firewall). | ||
22 | |||
23 | Use environments in module definitions or declare all local and create the | ||
24 | function with exported symbols later? | ||
25 | |||
26 | local P = {} | ||
27 | complex = P | ||
28 | setfenv(1, P) | ||
29 | |||
30 | Modules should return their namespace table in the end of the chunk. | ||
31 | |||
1 | Adjust dates in all files | 32 | Adjust dates in all files |
2 | 33 | ||
3 | Test the library on every system possible | 34 | Test the library on every system possible |
@@ -7,7 +38,7 @@ Document socket.time and socket.sleep | |||
7 | Implement time critical stuff from code module in C. | 38 | Implement time critical stuff from code module in C. |
8 | Add service name translation. | 39 | Add service name translation. |
9 | 40 | ||
10 | Ajeitar o protocolo da lua_socketlibopen()... | 41 | Ajeitar 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. | 2 | if not LUASOCKET_LIBNAME then error('module requires LuaSocket') end |
3 | -- LuaSocket toolkit | 3 | -- get LuaSocket namespace |
4 | -- Author: Diego Nehab | 4 | local socket = _G[LUASOCKET_LIBNAME] |
5 | -- Conforming to: RFC 821, LTN7 | 5 | if not socket then error('module requires LuaSocket') end |
6 | -- RCS ID: $Id$ | 6 | -- create smtp namespace inside LuaSocket namespace |
7 | ----------------------------------------------------------------------------- | 7 | local smtp = {} |
8 | 8 | socket.smtp = smtp | |
9 | local Public, Private = {}, {} | 9 | -- make all module globals fall into smtp namespace |
10 | local socket = _G[LUASOCKET_LIBNAME] -- get LuaSocket namespace | 10 | setmetatable(smtp, { __index = _G }) |
11 | socket.smtp = Public -- create smtp sub namespace | 11 | setfenv(1, smtp) |
12 | 12 | ||
13 | ----------------------------------------------------------------------------- | 13 | -- default port |
14 | -- Program constants | 14 | PORT = 25 |
15 | ----------------------------------------------------------------------------- | ||
16 | -- timeout in secconds before we give up waiting | ||
17 | Public.TIMEOUT = 180 | ||
18 | -- port used for connection | ||
19 | Public.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 |
22 | Public.DOMAIN = os.getenv("SERVER_NAME") or "localhost" | 17 | DOMAIN = os.getenv("SERVER_NAME") or "localhost" |
23 | -- default server used to send e-mails | 18 | -- default server used to send e-mails |
24 | Public.SERVER = "localhost" | 19 | SERVER = "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 | 22 | local 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 | ----------------------------------------------------------------------------- | ||
34 | function Private.try_receive(sock, pattern) | ||
35 | local data, err = sock:receive(pattern) | ||
36 | if not data then sock:close() end | ||
37 | return data, err | ||
38 | end | ||
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 | ----------------------------------------------------------------------------- | ||
47 | function Private.try_send(sock, data) | ||
48 | local sent, err = sock:send(data) | ||
49 | if not sent then sock:close() end | ||
50 | return err | ||
51 | end | 27 | end |
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) | 30 | local 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 | ----------------------------------------------------------------------------- | ||
62 | function 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) | ||
67 | end | 35 | end |
68 | 36 | ||
69 | ----------------------------------------------------------------------------- | 37 | -- gets server reply |
70 | -- Gets command reply, (accepts multiple-line replies) | 38 | local 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 |
77 | function 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 |
93 | end | 58 | end |
94 | 59 | ||
95 | ----------------------------------------------------------------------------- | 60 | -- metatable for server connection object |
96 | -- Checks if a message reply code is correct. Closes control openion | 61 | local metatable = { __index = {} } |
97 | -- if not. | 62 | |
98 | -- Input | 63 | -- switch handler for execute function |
99 | -- control: control openion socket | 64 | local 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 | 67 | function 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 |
105 | function 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 | ||
116 | end | 76 | end |
117 | 77 | ||
118 | ----------------------------------------------------------------------------- | 78 | -- stub for invalid instructions |
119 | -- Sends initial client greeting | 79 | function 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 | ----------------------------------------------------------------------------- | ||
126 | function 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) | ||
130 | end | 81 | end |
131 | 82 | ||
132 | ----------------------------------------------------------------------------- | 83 | -- execute the "command" instruction |
133 | -- Sends openion termination command | 84 | function 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 | ----------------------------------------------------------------------------- | ||
140 | function 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 | ||
146 | end | ||
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 | ----------------------------------------------------------------------------- | ||
157 | function 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) | ||
162 | end | 90 | end |
163 | 91 | ||
164 | ----------------------------------------------------------------------------- | 92 | function 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 |
172 | function 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") | ||
181 | end | 104 | end |
182 | 105 | ||
183 | ----------------------------------------------------------------------------- | 106 | -- finds out what instruction are we dealing with |
184 | -- Sends message mime headers and body | 107 | local 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 | ----------------------------------------------------------------------------- | ||
193 | function 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) | ||
206 | end | 113 | end |
207 | 114 | ||
208 | ----------------------------------------------------------------------------- | 115 | -- execute a list of instructions |
209 | -- Sends recipient list command | 116 | function 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 |
217 | function 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 |
229 | end | 126 | end |
230 | 127 | ||
231 | ----------------------------------------------------------------------------- | 128 | -- closes the underlying connection |
232 | -- Starts the connection and greets server | 129 | function 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 | ----------------------------------------------------------------------------- | ||
239 | function 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 | ||
254 | end | 131 | end |
255 | 132 | ||
256 | ----------------------------------------------------------------------------- | 133 | -- connect with server and return a smtp connection object |
257 | -- Sends a message using an opened server | 134 | function 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 | ----------------------------------------------------------------------------- | ||
269 | function Private.send(sock, message) | ||
270 | local code, answer | ||
271 | |||
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) | ||
279 | end | 138 | end |
280 | 139 | ||
281 | ----------------------------------------------------------------------------- | 140 | -- simple test drive |
282 | -- Closes connection with server | 141 | |
283 | -- Input | 142 | --[[ |
284 | -- sock: socket connected to server | 143 | c, m = connect("localhost") |
285 | -- Returns | 144 | assert(c, m) |
286 | -- code: server status code, nil if error | 145 | assert(c:execute {check = "2.." }) |
287 | -- answer: complete server reply | 146 | assert(c:execute {{command = "EHLO", argument = "localhost"}, {check = "2.."}}) |
288 | ----------------------------------------------------------------------------- | 147 | assert(c:execute {command = "MAIL", argument = "FROM:<diego@princeton.edu>"}) |
289 | function Private.close(sock) | 148 | assert(c:execute {check = "2.."}) |
290 | -- QUIT | 149 | assert(c:execute {command = "RCPT", argument = "TO:<diego@cs.princeton.edu>"}) |
291 | return Private.send_quit(sock) | 150 | assert(c:execute {check = function (code) return code == "250" end}) |
292 | end | 151 | assert(c:execute {{command = "DATA"}, {check = "3.."}}) |
293 | 152 | assert(c:execute {{raw = "This is the message\r\n.\r\n"}, {check = "2.."}}) | |
294 | ----------------------------------------------------------------------------- | 153 | assert(c:execute {{command = "QUIT"}, {check = "2.."}}) |
295 | -- Main mail function | 154 | c:close() |
296 | -- Input | 155 | ]] |
297 | -- message: a table with the following fields: | 156 | |
298 | -- from: message sender | 157 | return 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 | ----------------------------------------------------------------------------- | ||
306 | function 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 | ||
314 | end | ||