util-soc-imp-smtp.lua /size: 7018 b    last modification: 2020-07-01 14:35
1
-- original file : smtp.lua
2
-- for more into : see util-soc.lua
3 4
local
type
,
setmetatable
,
next
=
type
,
setmetatable
,
next
5
local
find
,
lower
,
format
=
string
.
find
,
string
.
lower
,
string
.
format
6
local
osdate
,
osgetenv
=
os
.
date
,
os
.
getenv
7
local
random
=
math
.
random
8 9
local
socket
=
socket
or
require
(
"
socket
"
)
10
local
headers
=
socket
.
headers
or
require
(
"
socket.headers
"
)
11
local
ltn12
=
ltn12
or
require
(
"
ltn12
"
)
12
local
tp
=
socket
.
tp
or
require
(
"
socket.tp
"
)
13
local
mime
=
mime
or
require
(
"
mime
"
)
14 15
local
mimeb64
=
mime
.
b64
16
local
mimestuff
=
mime
.
stuff
17 18
local
skipsocket
=
socket
.
skip
19
local
trysocket
=
socket
.
try
20
local
newtrysocket
=
socket
.
newtry
21
local
protectsocket
=
socket
.
protect
22 23
local
normalizeheaders
=
headers
.
normalize
24
local
lowerheaders
=
headers
.
lower
25 26
local
createcoroutine
=
coroutine
.
create
27
local
resumecoroutine
=
coroutine
.
resume
28
local
yieldcoroutine
=
coroutine
.
resume
29 30
local
smtp
=
{
31
TIMEOUT
=
60
,
32
SERVER
=
"
localhost
"
,
33
PORT
=
25
,
34
DOMAIN
=
osgetenv
(
"
SERVER_NAME
"
)
or
"
localhost
"
,
35
ZONE
=
"
-0000
"
,
36
}
37 38
socket
.
smtp
=
smtp
39 40
local
methods
=
{
}
41
local
mt
=
{
__index
=
methods
}
42 43
function
methods
.
greet
(
self
,
domain
)
44
local
try
=
self
.
try
45
local
tp
=
self
.
tp
46
try
(
tp
:
check
(
"
2..
"
)
)
47
try
(
tp
:
command
(
"
EHLO
"
,
domain
or
_M
.
DOMAIN
)
)
48
return
skipsocket
(
1
,
try
(
tp
:
check
(
"
2..
"
)
)
)
49
end
50 51
function
methods
.
mail
(
self
,
from
)
52
local
try
=
self
.
try
53
local
tp
=
self
.
tp
54
try
(
tp
:
command
(
"
MAIL
"
,
"
FROM:
"
.
.
from
)
)
55
return
try
(
tp
:
check
(
"
2..
"
)
)
56
end
57 58
function
methods
.
rcpt
(
self
,
to
)
59
local
try
=
self
.
try
60
local
tp
=
self
.
tp
61
try
(
tp
:
command
(
"
RCPT
"
,
"
TO:
"
.
.
to
)
)
62
return
try
(
tp
:
check
(
"
2..
"
)
)
63
end
64 65
function
methods
.
data
(
self
,
src
,
step
)
66
local
try
=
self
.
try
67
local
tp
=
self
.
tp
68
try
(
tp
:
command
(
"
DATA
"
)
)
69
try
(
tp
:
check
(
"
3..
"
)
)
70
try
(
tp
:
source
(
src
,
step
)
)
71
try
(
tp
:
send
(
"
\r\n.\r\n
"
)
)
72
return
try
(
tp
:
check
(
"
2..
"
)
)
73
end
74 75
function
methods
.
quit
(
self
)
76
local
try
=
self
.
try
77
local
tp
=
self
.
tp
78
try
(
tp
:
command
(
"
QUIT
"
)
)
79
return
try
(
tp
:
check
(
"
2..
"
)
)
80
end
81 82
function
methods
.
close
(
self
)
83
return
self
.
tp
:
close
(
)
84
end
85 86
function
methods
.
login
(
self
,
user
,
password
)
87
local
try
=
self
.
try
88
local
tp
=
self
.
tp
89
try
(
tp
:
command
(
"
AUTH
"
,
"
LOGIN
"
)
)
90
try
(
tp
:
check
(
"
3..
"
)
)
91
try
(
tp
:
send
(
mimeb64
(
user
)
.
.
"
\r\n
"
)
)
92
try
(
tp
:
check
(
"
3..
"
)
)
93
try
(
tp
:
send
(
mimeb64
(
password
)
.
.
"
\r\n
"
)
)
94
return
try
(
tp
:
check
(
"
2..
"
)
)
95
end
96 97
function
methods
.
plain
(
self
,
user
,
password
)
98
local
try
=
self
.
try
99
local
tp
=
self
.
tp
100
local
auth
=
"
PLAIN
"
.
.
mimeb64
(
"
\0
"
.
.
user
.
.
"
\0
"
.
.
password
)
101
try
(
tp
:
command
(
"
AUTH
"
,
auth
)
)
102
return
try
(
tp
:
check
(
"
2..
"
)
)
103
end
104 105
function
methods
.
auth
(
self
,
user
,
password
,
ext
)
106
if
not
user
or
not
password
then
107
return
1
108
end
109
local
try
=
self
.
try
110
if
find
(
ext
,
"
AUTH[^\n]+LOGIN
"
)
then
111
return
self
:
login
(
user
,
password
)
112
elseif
find
(
ext
,
"
AUTH[^\n]+PLAIN
"
)
then
113
return
self
:
plain
(
user
,
password
)
114
else
115
try
(
nil
,
"
authentication not supported
"
)
116
end
117
end
118 119
function
methods
.
send
(
self
,
mail
)
120
self
:
mail
(
mail
.
from
)
121
local
receipt
=
mail
.
rcpt
122
if
type
(
receipt
)
=
=
"
table
"
then
123
for
i
=
1
,
#
receipt
do
124
self
:
rcpt
(
receipt
[
i
]
)
125
end
126
elseif
receipt
then
127
self
:
rcpt
(
receipt
)
128
end
129
self
:
data
(
ltn12
.
source
.
chain
(
mail
.
source
,
mimestuff
(
)
)
,
mail
.
step
)
130
end
131 132
local
function
opensmtp
(
self
,
server
,
port
,
create
)
133
if
not
server
or
server
=
=
"
"
then
134
server
=
smtp
.
SERVER
135
end
136
if
not
port
or
port
=
=
"
"
then
137
port
=
smtp
.
PORT
138
end
139
local
s
=
{
140
tp
=
trysocket
(
tp
.
connect
(
server
,
port
,
smtp
.
TIMEOUT
,
create
)
)
,
141
try
=
newtrysocket
(
function
(
)
142
s
:
close
(
)
143
end
)
,
144
}
145
setmetatable
(
s
,
mt
)
146
return
s
147
end
148 149
smtp
.
open
=
opensmtp
150 151
local
nofboundaries
=
0
152 153
local
function
newboundary
(
)
154
nofboundaries
=
nofboundaries
+
1
155
return
format
(
'
%s%05d==%05u
'
,
osdate
(
'
%d%m%Y%H%M%S
'
)
,
random
(
0
,
99999
)
,
nofboundaries
)
156
end
157 158
local
send_message
159 160
local
function
send_headers
(
headers
)
161
yieldcoroutine
(
normalizeheaders
(
headers
)
)
162
end
163 164
local
function
send_multipart
(
message
)
165
local
boundary
=
newboundary
(
)
166
local
headers
=
lowerheaders
(
message
.
headers
)
167
local
body
=
message
.
body
168
local
preamble
=
body
.
preamble
169
local
epilogue
=
body
.
epilogue
170
local
content
=
headers
[
'
content-type
'
]
or
'
multipart/mixed
'
171
headers
[
'
content-type
'
]
=
content
.
.
'
; boundary="
'
.
.
boundary
.
.
'
"
'
172
send_headers
(
headers
)
173
if
preamble
then
174
yieldcoroutine
(
preamble
)
175
yieldcoroutine
(
"
\r\n
"
)
176
end
177
for
i
=
1
,
#
body
do
178
yieldcoroutine
(
"
\r\n--
"
.
.
boundary
.
.
"
\r\n
"
)
179
send_message
(
body
[
i
]
)
180
end
181
yieldcoroutine
(
"
\r\n--
"
.
.
boundary
.
.
"
--\r\n\r\n
"
)
182
if
epilogue
then
183
yieldcoroutine
(
epilogue
)
184
yieldcoroutine
(
"
\r\n
"
)
185
end
186
end
187 188
local
default_content_type
=
'
text/plain; charset="UTF-8"
'
189 190
local
function
send_source
(
message
)
191
local
headers
=
lowerheaders
(
message
.
headers
)
192
if
not
headers
[
'
content-type
'
]
then
193
headers
[
'
content-type
'
]
=
default_content_type
194
end
195
send_headers
(
headers
)
196
local
getchunk
=
message
.
body
197
while
true
do
198
local
chunk
,
err
=
getchunk
(
)
199
if
err
then
200
yieldcoroutine
(
nil
,
err
)
201
elseif
chunk
then
202
yieldcoroutine
(
chunk
)
203
else
204
break
205
end
206
end
207
end
208 209
local
function
send_string
(
message
)
210
local
headers
=
lowerheaders
(
message
.
headers
)
211
if
not
headers
[
'
content-type
'
]
then
212
headers
[
'
content-type
'
]
=
default_content_type
213
end
214
send_headers
(
headers
)
215
yieldcoroutine
(
message
.
body
)
216
end
217 218
function
send_message
(
message
)
219
local
body
=
message
.
body
220
if
type
(
body
)
=
=
"
table
"
then
221
send_multipart
(
message
)
222
elseif
type
(
body
)
=
=
"
function
"
then
223
send_source
(
message
)
224
else
225
send_string
(
message
)
226
end
227
end
228 229
local
function
adjust_headers
(
message
)
230
local
headers
=
lowerheaders
(
message
.
headers
)
231
if
not
headers
[
"
date
"
]
then
232
headers
[
"
date
"
]
=
osdate
(
"
!%a, %d %b %Y %H:%M:%S
"
)
.
.
(
message
.
zone
or
smtp
.
ZONE
)
233
end
234
if
not
headers
[
"
x-mailer
"
]
then
235
headers
[
"
x-mailer
"
]
=
socket
.
_VERSION
236
end
237
headers
[
"
mime-version
"
]
=
"
1.0
"
238
return
headers
239
end
240 241
function
smtp
.
message
(
message
)
242
message
.
headers
=
adjust_headers
(
message
)
243
local
action
=
createcoroutine
(
function
(
)
244
send_message
(
message
)
245
end
)
246
return
function
(
)
247
local
ret
,
a
,
b
=
resumecoroutine
(
action
)
248
if
ret
then
249
return
a
,
b
250
else
251
return
nil
,
a
252
end
253
end
254
end
255 256
smtp
.
send
=
protectsocket
(
function
(
mail
)
257
local
snd
=
opensmtp
(
smtp
,
mail
.
server
,
mail
.
port
,
mail
.
create
)
258
local
ext
=
snd
:
greet
(
mail
.
domain
)
259
snd
:
auth
(
mail
.
user
,
mail
.
password
,
ext
)
260
snd
:
send
(
mail
)
261
snd
:
quit
(
)
262
return
snd
:
close
(
)
263
end
)
264 265
package
.
loaded
[
"
socket.smtp
"
]
=
smtp
266 267
return
smtp
268