diff options
Diffstat (limited to 'src/smtp.lua')
-rw-r--r-- | src/smtp.lua | 338 |
1 files changed, 338 insertions, 0 deletions
diff --git a/src/smtp.lua b/src/smtp.lua new file mode 100644 index 0000000..f9ed64c --- /dev/null +++ b/src/smtp.lua | |||
@@ -0,0 +1,338 @@ | |||
1 | ----------------------------------------------------------------------------- | ||
2 | -- Simple SMTP support for the Lua language using the LuaSocket toolkit. | ||
3 | -- Author: Diego Nehab | ||
4 | -- Date: 26/12/2000 | ||
5 | -- Conforming to: RFC 821 | ||
6 | ----------------------------------------------------------------------------- | ||
7 | |||
8 | ----------------------------------------------------------------------------- | ||
9 | -- Program constants | ||
10 | ----------------------------------------------------------------------------- | ||
11 | -- timeout in secconds before we give up waiting | ||
12 | local TIMEOUT = 180 | ||
13 | -- port used for connection | ||
14 | local PORT = 25 | ||
15 | -- domain used in HELO command. If we are under a CGI, try to get from | ||
16 | -- environment | ||
17 | local DOMAIN = getenv("SERVER_NAME") | ||
18 | if not DOMAIN then | ||
19 | DOMAIN = "localhost" | ||
20 | end | ||
21 | |||
22 | ----------------------------------------------------------------------------- | ||
23 | -- Tries to send DOS mode lines. Closes socket on error. | ||
24 | -- Input | ||
25 | -- sock: server socket | ||
26 | -- line: string to be sent | ||
27 | -- Returns | ||
28 | -- err: message in case of error, nil if successfull | ||
29 | ----------------------------------------------------------------------------- | ||
30 | local puts = function(sock, line) | ||
31 | local err = sock:send(line .. "\r\n") | ||
32 | if err then sock:close() end | ||
33 | return err | ||
34 | end | ||
35 | |||
36 | ----------------------------------------------------------------------------- | ||
37 | -- Tries to receive DOS mode lines. Closes socket on error. | ||
38 | -- Input | ||
39 | -- sock: server socket | ||
40 | -- Returns | ||
41 | -- line: received string if successfull, nil in case of error | ||
42 | -- err: error message if any | ||
43 | ----------------------------------------------------------------------------- | ||
44 | local gets = function(sock) | ||
45 | local line, err = sock:receive("*l") | ||
46 | if err then | ||
47 | sock:close() | ||
48 | return nil, err | ||
49 | end | ||
50 | return line | ||
51 | end | ||
52 | |||
53 | ----------------------------------------------------------------------------- | ||
54 | -- Gets a reply from the server and close connection if it is wrong | ||
55 | -- Input | ||
56 | -- sock: server socket | ||
57 | -- accept: acceptable errorcodes | ||
58 | -- Returns | ||
59 | -- code: server reply code. nil if error | ||
60 | -- line: complete server reply message or error message | ||
61 | ----------------------------------------------------------------------------- | ||
62 | local get_reply = function(sock, accept) | ||
63 | local line, err = %gets(sock) | ||
64 | if line then | ||
65 | if type(accept) ~= "table" then accept = {accept} end | ||
66 | local _,_, code = strfind(line, "^(%d%d%d)") | ||
67 | if not code then return nil, line end | ||
68 | code = tonumber(code) | ||
69 | for i = 1, getn(accept) do | ||
70 | if code == accept[i] then return code, line end | ||
71 | end | ||
72 | sock:close() | ||
73 | return nil, line | ||
74 | end | ||
75 | return nil, err | ||
76 | end | ||
77 | |||
78 | ----------------------------------------------------------------------------- | ||
79 | -- Sends a command to the server | ||
80 | -- Input | ||
81 | -- sock: server socket | ||
82 | -- command: command to be sent | ||
83 | -- param: command parameters if any | ||
84 | -- Returns | ||
85 | -- err: error message if any | ||
86 | ----------------------------------------------------------------------------- | ||
87 | local send_command = function(sock, command, param) | ||
88 | local line | ||
89 | if param then line = command .. " " .. param | ||
90 | else line = command end | ||
91 | return %puts(sock, line) | ||
92 | end | ||
93 | |||
94 | ----------------------------------------------------------------------------- | ||
95 | -- Gets the initial server greeting | ||
96 | -- Input | ||
97 | -- sock: server socket | ||
98 | -- Returns | ||
99 | -- code: server status code, nil if error | ||
100 | -- answer: complete server reply | ||
101 | ----------------------------------------------------------------------------- | ||
102 | local get_helo = function(sock) | ||
103 | return %get_reply(sock, 220) | ||
104 | end | ||
105 | |||
106 | ----------------------------------------------------------------------------- | ||
107 | -- Sends initial client greeting | ||
108 | -- Input | ||
109 | -- sock: server socket | ||
110 | -- Returns | ||
111 | -- code: server status code, nil if error | ||
112 | -- answer: complete server reply | ||
113 | ----------------------------------------------------------------------------- | ||
114 | local send_helo = function(sock) | ||
115 | local err = %send_command(sock, "HELO", %DOMAIN) | ||
116 | if not err then | ||
117 | return %get_reply(sock, 250) | ||
118 | else return nil, err end | ||
119 | end | ||
120 | |||
121 | ----------------------------------------------------------------------------- | ||
122 | -- Sends mime headers | ||
123 | -- Input | ||
124 | -- sock: server socket | ||
125 | -- mime: table with mime headers to be sent | ||
126 | -- Returns | ||
127 | -- err: error message if any | ||
128 | ----------------------------------------------------------------------------- | ||
129 | local send_mime = function(sock, mime) | ||
130 | local err | ||
131 | mime = mime or {} | ||
132 | -- send all headers | ||
133 | for name,value in mime do | ||
134 | err = sock:send(name .. ": " .. value .. "\r\n") | ||
135 | if err then | ||
136 | sock:close() | ||
137 | return err | ||
138 | end | ||
139 | end | ||
140 | -- end mime part | ||
141 | err = sock:send("\r\n") | ||
142 | if err then sock:close() end | ||
143 | return err | ||
144 | end | ||
145 | |||
146 | ----------------------------------------------------------------------------- | ||
147 | -- Sends connection termination command | ||
148 | -- Input | ||
149 | -- sock: server socket | ||
150 | -- Returns | ||
151 | -- code: server status code, nil if error | ||
152 | -- answer: complete server reply | ||
153 | ----------------------------------------------------------------------------- | ||
154 | local send_quit = function(sock) | ||
155 | local code, answer | ||
156 | local err = %send_command(sock, "QUIT") | ||
157 | if not err then | ||
158 | code, answer = %get_reply(sock, 221) | ||
159 | sock:close() | ||
160 | return code, answer | ||
161 | else return nil, err end | ||
162 | end | ||
163 | |||
164 | ----------------------------------------------------------------------------- | ||
165 | -- Sends sender command | ||
166 | -- Input | ||
167 | -- sock: server socket | ||
168 | -- sender: e-mail of sender | ||
169 | -- Returns | ||
170 | -- code: server status code, nil if error | ||
171 | -- answer: complete server reply | ||
172 | ----------------------------------------------------------------------------- | ||
173 | local send_mail = function(sock, sender) | ||
174 | local param = format("FROM:<%s>", sender) | ||
175 | local err = %send_command(sock, "MAIL", param) | ||
176 | if not err then | ||
177 | return %get_reply(sock, 250) | ||
178 | else return nil, err end | ||
179 | end | ||
180 | |||
181 | ----------------------------------------------------------------------------- | ||
182 | -- Sends message mime headers and body | ||
183 | -- Input | ||
184 | -- sock: server socket | ||
185 | -- mime: table containing all mime headers to be sent | ||
186 | -- body: message body | ||
187 | -- Returns | ||
188 | -- code: server status code, nil if error | ||
189 | -- answer: complete server reply | ||
190 | ----------------------------------------------------------------------------- | ||
191 | local send_data = function (sock, mime, body) | ||
192 | local err = %send_command(sock, "DATA") | ||
193 | if not err then | ||
194 | local code, answer = %get_reply(sock, 354) | ||
195 | if not code then return nil, answer end | ||
196 | -- avoid premature end in message body | ||
197 | body = gsub(body or "", "\n%.", "\n%.%.") | ||
198 | -- mark end of message body | ||
199 | body = body .. "\r\n." | ||
200 | err = %send_mime(sock, mime) | ||
201 | if err then return nil, err end | ||
202 | err = %puts(sock, body) | ||
203 | return %get_reply(sock, 250) | ||
204 | else return nil, err end | ||
205 | end | ||
206 | |||
207 | ----------------------------------------------------------------------------- | ||
208 | -- Sends recipient list command | ||
209 | -- Input | ||
210 | -- sock: server socket | ||
211 | -- rcpt: lua table with recipient list | ||
212 | -- Returns | ||
213 | -- code: server status code, nil if error | ||
214 | -- answer: complete server reply | ||
215 | ----------------------------------------------------------------------------- | ||
216 | local send_rcpt = function(sock, rcpt) | ||
217 | local err, code, answer | ||
218 | if type(rcpt) ~= "table" then rcpt = {rcpt} end | ||
219 | for i = 1, getn(rcpt) do | ||
220 | err = %send_command(sock, "RCPT", format("TO:<%s>", rcpt[i])) | ||
221 | if not err then | ||
222 | code, answer = %get_reply(sock, {250, 251}) | ||
223 | if not code then return code, answer end | ||
224 | else return nil, err end | ||
225 | end | ||
226 | return code, answer | ||
227 | end | ||
228 | |||
229 | ----------------------------------------------------------------------------- | ||
230 | -- Sends verify recipient command | ||
231 | -- Input | ||
232 | -- sock: server socket | ||
233 | -- user: user to be verified | ||
234 | -- Returns | ||
235 | -- code: server status code, nil if error | ||
236 | -- answer: complete server reply | ||
237 | ----------------------------------------------------------------------------- | ||
238 | local send_vrfy = function (sock, user) | ||
239 | local err = %send_command(sock, "VRFY", format("<%s>", user)) | ||
240 | if not err then | ||
241 | return %get_reply(sock, {250, 251}) | ||
242 | else return nil, err end | ||
243 | end | ||
244 | |||
245 | ----------------------------------------------------------------------------- | ||
246 | -- Connection oriented mail functions | ||
247 | ----------------------------------------------------------------------------- | ||
248 | function smtp_connect(server) | ||
249 | local code, answer | ||
250 | -- connect to server | ||
251 | local sock, err = connect(server, %PORT) | ||
252 | if not sock then return nil, err end | ||
253 | sock:timeout(%TIMEOUT) | ||
254 | -- initial server greeting | ||
255 | code, answer = %get_helo(sock) | ||
256 | if not code then return nil, answer end | ||
257 | -- HELO | ||
258 | code, answer = %send_helo(sock) | ||
259 | if not code then return nil, answer end | ||
260 | return sock | ||
261 | end | ||
262 | |||
263 | function smtp_send(sock, from, rcpt, mime, body) | ||
264 | local code, answer | ||
265 | |||
266 | code, answer = %send_mail(sock, from) | ||
267 | if not code then return nil, answer end | ||
268 | -- RCPT | ||
269 | code, answer = %send_rcpt(sock, rcpt) | ||
270 | if not code then return nil, answer end | ||
271 | -- DATA | ||
272 | return %send_data(sock, mime, body) | ||
273 | end | ||
274 | |||
275 | function smtp_close(sock) | ||
276 | -- QUIT | ||
277 | return %send_quit(sock) | ||
278 | end | ||
279 | |||
280 | ----------------------------------------------------------------------------- | ||
281 | -- Main mail function | ||
282 | -- Input | ||
283 | -- from: message sender | ||
284 | -- rcpt: table containing message recipients | ||
285 | -- mime: table containing mime headers | ||
286 | -- body: message body | ||
287 | -- server: smtp server to be used | ||
288 | -- Returns | ||
289 | -- nil if successfull, error message in case of error | ||
290 | ----------------------------------------------------------------------------- | ||
291 | function smtp_mail(from, rcpt, mime, body, server) | ||
292 | local sock, err = smtp_connect(server) | ||
293 | if not sock then return err end | ||
294 | local code, answer = smtp_send(sock, from, rcpt, mime, body) | ||
295 | if not code then return answer end | ||
296 | code, answer = smtp_close(sock) | ||
297 | if not code then return answer | ||
298 | else return nil end | ||
299 | end | ||
300 | |||
301 | --=========================================================================== | ||
302 | -- Compatibility functions | ||
303 | --=========================================================================== | ||
304 | ----------------------------------------------------------------------------- | ||
305 | -- Converts a comma separated list into a Lua table with one entry for each | ||
306 | -- list element. | ||
307 | -- Input | ||
308 | -- str: string containing the list to be converted | ||
309 | -- tab: table to be filled with entries | ||
310 | -- Returns | ||
311 | -- a table t, where t.n is the number of elements with an entry t[i] | ||
312 | -- for each element | ||
313 | ----------------------------------------------------------------------------- | ||
314 | local fill = function(str, tab) | ||
315 | gsub(str, "([^%s,]+)", function (w) tinsert(%tab, w) end) | ||
316 | return tab | ||
317 | end | ||
318 | |||
319 | ----------------------------------------------------------------------------- | ||
320 | -- Client mail function, implementing CGILUA 3.2 interface | ||
321 | ----------------------------------------------------------------------------- | ||
322 | function mail(msg) | ||
323 | local rcpt = {} | ||
324 | local mime = {} | ||
325 | mime["Subject"] = msg.subject | ||
326 | mime["To"] = msg.to | ||
327 | mime["From"] = msg.from | ||
328 | %fill(msg.to, rcpt) | ||
329 | if msg.cc then | ||
330 | %fill(msg.cc, rcpt) | ||
331 | mime["Cc"] = msg.cc | ||
332 | end | ||
333 | if msg.bcc then | ||
334 | %fill(msg.bcc, rcpt) | ||
335 | end | ||
336 | rcpt.n = nil | ||
337 | return %smtp_mail(msg.from, rcpt, mime, msg.message, msg.mailserver) | ||
338 | end | ||