-
Notifications
You must be signed in to change notification settings - Fork 3
/
tls.lua
395 lines (331 loc) · 11.8 KB
/
tls.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
-- ***************************************************************
--
-- Copyright 2018 by Sean Conner. All Rights Reserved.
--
-- This library is free software; you can redistribute it and/or modify it
-- under the terms of the GNU Lesser General Public License as published by
-- the Free Software Foundation; either version 3 of the License, or (at your
-- option) any later version.
--
-- This library is distributed in the hope that it will be useful, but
-- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
-- or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
-- License for more details.
--
-- You should have received a copy of the GNU Lesser General Public License
-- along with this library; if not, see <http://www.gnu.org/licenses/>.
--
-- Comments, questions and criticisms can be sent to: sean@conman.org
--
-- ********************************************************************
-- luacheck: globals listens listena listen connecta connect
-- luacheck: ignore 611
--
-- We require org.conman.tls.LIBRESSL_VERSION >= 0x2050000f
local syslog = require "org.conman.syslog"
local errno = require "org.conman.errno"
local mkios = require "org.conman.net.ios"
local net = require "org.conman.net"
local tls = require "org.conman.tls"
local nfl = require "org.conman.nfl"
local coroutine = require "coroutine"
local _VERSION = _VERSION
local assert = assert
local setmetatable = setmetatable
local ipairs = ipairs
if _VERSION == "Lua 5.1" then
module(...)
else
_ENV = {} -- luacheck: ignore
end
-- **********************************************************************
local function create_handler(conn,remote)
local ios = mkios()
ios.__socket = conn
ios.__remote = remote
ios.__input = ""
ios.__rbytesraw = 0
ios.__rbytes = 0
ios.__wbytesraw = 0
ios.__wbytes = 0
ios._handshake = function(self)
local rc = ios.__ctx:handshake()
if rc == tls.WANT_INPUT then
coroutine.yield()
return self:_handshake()
elseif rc == tls.WANT_OUTPUT then
nfl.SOCKETS:update(self.__socket,"w")
coroutine.yield()
return self:_handshake()
else
return rc == 0
end
end
ios._refill = function(self)
local str,len = self.__ctx:read(tls.BUFFERSIZE)
if len == tls.ERROR then
return nil,self.__ctx:error(),-1
elseif len == tls.WANT_INPUT then
coroutine.yield()
return self:_refill()
elseif len == tls.WANT_OUTPUT then
nfl.SOCKETS:update(self.__socket,"w")
coroutine.yield()
return self:_refill()
else
if #str == 0 then
return nil
else
ios.__rbytes = ios.__rbytes + #str
return str
end
end
end
ios._drain = function(self,data)
local bytes = self.__ctx:write(data)
if bytes == tls.ERROR then
-- --------------------------------------------------------------------
-- I was receiving "Resource temporarily unavailable" and trying again,
-- but that strategy fails upon failure to read a certificate. So now
-- I'm back to returning an error. Let's hope this works this time.
-- --------------------------------------------------------------------
return false,self.__ctx:error(),-1
elseif bytes == tls.WANT_INPUT then
coroutine.yield()
return self:_drain(data)
elseif bytes == tls.WANT_OUTPUT then
nfl.SOCKETS:update(self.__socket,"w")
coroutine.yield()
return self:_drain(data)
elseif bytes < #data then
ios.__wbytes = ios.__wbytes + bytes
nfl.SOCKETS:update(self.__socket,"w")
coroutine.yield()
return self:_drain(data:sub(bytes+1,-1))
else
ios.__wbytes = ios.__wbytes + bytes
end
return true
end
ios.close = function(self)
local rc = ios.__ctx:close()
if rc == tls.WANT_INPUT then
coroutine.yield()
return self:close()
elseif rc == tls.WANT_OUTPUT then
nfl.SOCKETS:update(self.__socket,"w")
coroutine.yield()
return self:close()
end
nfl.SOCKETS:remove(self.__socket)
local err = self.__socket:close()
return err == 0,errno[err],err
end
if _VERSION >= "Lua 5.2" then
local mt = {}
mt.__gc = ios.close
if _VERSION >= "Lua 5.4" then
mt.__close = ios.close
end
setmetatable(ios,mt)
end
ios:setvbuf('no')
return ios,function(event)
assert(not (event.read and event.write))
if event.hangup then
if not ios._eof then
nfl.SOCKETS:remove(ios.__socket)
ios._eof = true
nfl.schedule(ios.__co)
end
return
end
if event.read then
local _,packet,err = ios.__socket:recv()
if packet then
if #packet == 0 then
nfl.SOCKETS:remove(ios.__socket)
ios._eof = true
else
ios.__input = ios.__input .. packet
ios.__rbytesraw = ios.__rbytesraw + #packet
end
nfl.schedule(ios.__co)
else
syslog('error',"TLS.socket:recv() = %s",errno[err],err)
nfl.SOCKETS:remove(ios.__socket)
nfl.schedule(ios.__co,false,errno[err],err)
end
end
if event.write then
nfl.SOCKETS:update(ios.__socket,'r')
nfl.schedule(ios.__co,true)
end
end
end
-- **********************************************************************
--
-- Callbacks for accept_cbs() and connect_cbs().
--
-- **********************************************************************
local function tlscb_read(_,len,ios)
if #ios.__input == 0 then
if ios._eof then return "",0 end
return nil,tls.WANT_INPUT
end
local ret = ios.__input:sub(1,len)
ios.__input = ios.__input:sub(len + 1,-1)
return ret,0
end
-- **********************************************************************
local function tlscb_write(_,str,ios)
local bytes,err = ios.__socket:send(nil,str)
if bytes == -1 then
if err == errno.EAGAIN then
bytes = tls.WANT_OUTPUT
else
bytes = tls.ERROR
end
else
ios.__wbytesraw = ios.__wbytesraw + bytes
end
return bytes
end
-- **********************************************************************
-- Usage: sock,errmsg = listens(sock,mainf,conf)
-- Desc: Initialize a listening TCP socket
-- Input: sock (userdata/socket) bound socket
-- mainf (function) main handler for service
-- conf (function) function for TLS configuration
-- Return: sock (userdata) socket used for listening, false on error
-- errmsg (string) error message
-- **********************************************************************
function listens(sock,mainf,conf)
local config = tls.config()
local server = tls.server()
if not conf(config) then return false,config:error() end
if not server:configure(config) then
return false,server:error()
end
nfl.SOCKETS:insert(sock,'r',function()
local conn,remote,err = sock:accept()
if not conn then
syslog('error',"sock:accept() = %s",errno[err])
return
end
conn.nonblock = true
conn.nodelay = true
local ios,packet_handler = create_handler(conn,remote)
ios.__ctx = server:accept_cbs(ios,tlscb_read,tlscb_write)
ios.__co = nfl.spawn(mainf,ios)
nfl.SOCKETS:insert(conn,'r',packet_handler)
end)
return sock
end
-- **********************************************************************
-- Usage: sock,errmsg = listena(addr,mainf,conf)
-- Desc: Initialize a listening TCP socket
-- Input: addr (userdata/address) IP address
-- mainf (function) main handler for service
-- conf (function) function for TLS configuration
-- Return: sock (userdata) socket used for listening, false on error
-- errmsg (string) error message
-- **********************************************************************
function listena(addr,mainf,conf)
local sock,err = net.socket(addr.family,'tcp')
if not sock then
return false,errno[err]
end
sock.reuseaddr = true
sock.nonblock = true
sock:bind(addr)
sock:listen()
return listens(sock,mainf,conf)
end
-- **********************************************************************
-- Usage: sock,errmsg = listen(host,port,mainf,config)
-- Desc: Initalize a listening TCP socket
-- Input: host (string) address to bind to
-- port (string integer) port
-- mainf (function) main handler for service
-- config (function) configuration options
-- Return: sock (userdata) socket used for listening, false on error
-- errmsg (string) error message
-- **********************************************************************
function listen(host,port,mainf,config)
return listena(net.address2(host,'any','tcp',port)[1],mainf,config)
end
-- **********************************************************************
-- Usage: ios = tcp.connecta(addr,hostname[,to[,config]])
-- Desc: Connect to a remote address
-- Input: addr (userdata/address) IP address
-- hostname (string) hostname (required for TLS)
-- to (number/optinal) timout the operation after to seconds
-- config (function) configuration options
-- Return: ios (table) Input/Output object (nil on error)
-- **********************************************************************
function connecta(addr,hostname,to,conf)
if not addr then return nil end
local config = tls.config()
local ctx = tls.client()
if conf then
if not conf(config) then return false,config:error() end
else
config:protocols("all")
end
ctx:configure(config)
local sock,err = net.socket(addr.family,'tcp')
if not sock then
syslog('error',"tls(TCP) = %s",errno[err])
return false,errno[err]
end
sock.nonblock = true
local ios,packet_handler = create_handler(sock,addr)
ios.__ctx = ctx
ios.__co = coroutine.running()
if not ctx:connect_cbs(hostname,ios,tlscb_read,tlscb_write) then
syslog('error',"connect_cbs() = %s",ctx:error())
return false,ctx:error()
end
-- ------------------------------------------------------------
-- In POSIXland, a non-blocking socket doing a connect become available
-- when it's ready for writing. So we install a 'write' trigger, then
-- call connect() and yield. When we return, it's connected (unless we're
-- optionally timing out the operation).
-- ------------------------------------------------------------
nfl.SOCKETS:insert(sock,'w',packet_handler)
if to then nfl.timeout(to,false,errno.ETIMEDOUT) end
sock:connect(addr)
local okay,err1 = coroutine.yield()
if to then nfl.timeout(0) end
if not okay then
syslog('error',"tls:connect(%s) = %s",hostname,err1 or "(nil)")
nfl.SOCKETS:remove(sock)
return false,err1
end
return ios
end
-- **********************************************************************
-- Usage: ios = tcp.connect(host,port[,to[,conf]])
-- Desc: Connect to a remote host
-- Input: host (string) IP address
-- port (string number) port to connect to
-- to (number/optioal) timeout the operation after to seconds
-- conf (function) configuration options
-- Return: ios (table) Input/Output object (nil on error)
-- **********************************************************************
function connect(host,port,to,conf)
local addr = net.address2(host,'any','tcp',port)
if addr then
for _,a in ipairs(addr) do
local conn = connecta(a,host,to,conf)
if conn then
return conn
end
end
end
end
-- **********************************************************************
if _VERSION >= "Lua 5.2" then
return _ENV -- luacheck: ignore
end