• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tarantool / http / 19290332623

12 Nov 2025 07:55AM UTC coverage: 80.179% (+0.4%) from 79.789%
19290332623

push

github

themilchenko
Release 1.9.0

1 of 1 new or added line in 1 file covered. (100.0%)

51 existing lines in 2 files now uncovered.

987 of 1231 relevant lines covered (80.18%)

120.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.39
/http/sslsocket.lua
1
local TIMEOUT_INFINITY = 500 * 365 * 86400
19✔
2
local LIMIT_INFINITY   = 2147483647
19✔
3

4
local log = require('log')
19✔
5
local ffi = require('ffi')
19✔
6
local fio = require('fio')
19✔
7
local socket = require('socket')
19✔
8
local buffer = require('buffer')
19✔
9
local clock = require('clock')
19✔
10
local errno = require('errno')
19✔
11

12
pcall(ffi.cdef, 'typedef struct SSL_METHOD {} SSL_METHOD;')
19✔
13
pcall(ffi.cdef, 'typedef struct SSL_CTX {} SSL_CTX;')
19✔
14
pcall(ffi.cdef, 'typedef struct SSL {} SSL;')
19✔
15

16
pcall(ffi.cdef, [[
38✔
17
    const SSL_METHOD *TLS_server_method(void);
18
    const SSL_METHOD *TLS_client_method(void);
19

20
    SSL_CTX *SSL_CTX_new(const SSL_METHOD *method);
21
    void SSL_CTX_free(SSL_CTX *);
22

23
    int SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file, int type);
24
    int SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type);
25
    void SSL_CTX_set_default_passwd_cb_userdata(SSL_CTX *ctx, void *u);
26
    typedef int (*pem_passwd_cb)(char *buf, int size, int rwflag, void *userdata);
27

28
    void SSL_CTX_set_default_passwd_cb(SSL_CTX *ctx, pem_passwd_cb cb);
29

30
    int SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile,
31
                                      const char *CApath);
32
    int SSL_CTX_set_cipher_list(SSL_CTX *ctx, const char *str);
33
    void SSL_CTX_set_verify(SSL_CTX *ctx, int mode,
34
                            int (*verify_callback)(int, void *));
35

36
    SSL *SSL_new(SSL_CTX *ctx);
37
    void SSL_free(SSL *ssl);
38

39
    int SSL_set_fd(SSL *s, int fd);
40

41
    void SSL_set_connect_state(SSL *s);
42
    void SSL_set_accept_state(SSL *s);
43

44
    int SSL_write(SSL *ssl, const void *buf, int num);
45
    int SSL_read(SSL *ssl, void *buf, int num);
46

47
    int SSL_pending(const SSL *ssl);
48

49
    void ERR_clear_error(void);
50
    char *ERR_error_string(unsigned long e, char *buf);
51
    unsigned long ERR_peek_last_error(void);
52

53
    int SSL_get_error(const SSL *s, int ret_code);
54

55
    void *memmem(const void *haystack, size_t haystacklen,
56
                 const void *needle, size_t needlelen);
57
]])
19✔
58

59
local SET_VERIFY_FLAGS = {
19✔
60
    SSL_VERIFY_NONE = 0x00,
61
    SSL_VERIFY_PEER = 0x01,
62
    SSL_VERIFY_FAIL_IF_NO_PEER = 0x02,
63
}
64

65
local VERIFY_CLIENT_OPTS = {
19✔
66
    off = SET_VERIFY_FLAGS.SSL_VERIFY_NONE,
19✔
67
    optional = SET_VERIFY_FLAGS.SSL_VERIFY_PEER,
19✔
68
    on = bit.bor(SET_VERIFY_FLAGS.SSL_VERIFY_PEER, SET_VERIFY_FLAGS.SSL_VERIFY_FAIL_IF_NO_PEER),
19✔
69
}
70

71
local function slice_wait(timeout, starttime)
72
    if timeout == nil then
91✔
73
        return nil
16✔
74
    end
75

76
    return timeout - (clock.time() - starttime)
75✔
77
end
78

79
local X509_FILETYPE_PEM = 1
19✔
80

81
local function tls_server_method()
82
    return ffi.C.TLS_server_method()
32✔
83
end
84

85
local function ctx(method)
86
    ffi.C.ERR_clear_error()
51✔
87

88
    return ffi.gc(ffi.C.SSL_CTX_new(method), ffi.C.SSL_CTX_free)
51✔
89
end
90

91
local function ctx_use_private_key_file(ctx, pem_file, password, password_file)
92
    ffi.C.SSL_CTX_set_default_passwd_cb(ctx, box.NULL);
32✔
93

94
    if password ~= nil then
32✔
95
        log.info('set private key password')
2✔
96

97
        ffi.C.SSL_CTX_set_default_passwd_cb_userdata(ctx,
4✔
98
            ffi.cast('void*', ffi.cast('char*', password)))
2✔
99
        local rc = ffi.C.SSL_CTX_use_PrivateKey_file(ctx, pem_file, X509_FILETYPE_PEM)
2✔
100
        ffi.C.SSL_CTX_set_default_passwd_cb_userdata(ctx, box.NULL);
2✔
101

102
        if rc == 1 then
2✔
103
            return true
1✔
104
        end
105
        ffi.C.ERR_clear_error()
1✔
106
    end
107

108
    if password_file ~= nil then
31✔
109
        local fh = fio.open(password_file, {'O_RDONLY'})
15✔
110
        if fh == nil then
15✔
UNCOV
111
            return false
×
112
        end
113

114
        local is_loaded = false
15✔
115
        local password_from_file = ""
15✔
116
        local char
117
        while char ~= '' do
29✔
118
            while true do
119
                char = fh:read(1)
742✔
120
                if char == '\n' or char == '' then
371✔
121
                    break
122
                end
123
                password_from_file = password_from_file .. char
342✔
124
            end
125

126
            ffi.C.SSL_CTX_set_default_passwd_cb_userdata(ctx,
58✔
127
                ffi.cast('void*', ffi.cast('char*', password_from_file)))
29✔
128
            local rc = ffi.C.SSL_CTX_use_PrivateKey_file(ctx, pem_file, X509_FILETYPE_PEM)
29✔
129
            ffi.C.SSL_CTX_set_default_passwd_cb_userdata(ctx, box.NULL);
29✔
130

131
            if rc == 1 then
29✔
132
                is_loaded = true
15✔
133
                break
15✔
134
            end
135

136
            ffi.C.ERR_clear_error()
14✔
137
            password_from_file = ''
14✔
138
        end
139

140
        fh:close()
15✔
141

142
        return is_loaded
15✔
143
    end
144

145
    local rc = ffi.C.SSL_CTX_use_PrivateKey_file(ctx, pem_file, X509_FILETYPE_PEM)
16✔
146
    if rc ~= 1 then
16✔
147
        ffi.C.ERR_clear_error()
2✔
148
        return false
2✔
149
    end
150

151
    return true
14✔
152
end
153

154
local function ctx_use_certificate_file(ctx, pem_file)
155
    if ffi.C.SSL_CTX_use_certificate_file(ctx, pem_file, X509_FILETYPE_PEM) ~= 1 then
30✔
UNCOV
156
        ffi.C.ERR_clear_error()
×
UNCOV
157
        return false
×
158
    end
159
    return true
30✔
160
end
161

162
local function ctx_load_verify_locations(ctx, ca_file)
163
    if ffi.C.SSL_CTX_load_verify_locations(ctx, ca_file, box.NULL) ~= 1 then
19✔
164
        ffi.C.ERR_clear_error()
1✔
165
        return false
1✔
166
    end
167
    return true
18✔
168
end
169

170
local function ctx_set_cipher_list(ctx, str)
171
    if ffi.C.SSL_CTX_set_cipher_list(ctx, str) ~= 1 then
2✔
172
        return false
1✔
173
    end
174
    return true
1✔
175
end
176

177
local function ctx_set_verify(ctx, mode)
178
    mode = mode or 'off'
18✔
179
    ffi.C.SSL_CTX_set_verify(ctx, VERIFY_CLIENT_OPTS[mode], box.NULL)
18✔
180
end
181

182
local default_ctx = ctx(ffi.C.TLS_server_method())
19✔
183

184
local SSL_ERROR_WANT_READ   = 2
19✔
185
local SSL_ERROR_WANT_WRITE  = 3
19✔
186
local SSL_ERROR_SYSCALL     = 5 -- Look at error stack/return value/errno.
19✔
187
local SSL_ERROR_ZERO_RETURN = 6
19✔
188

189
local sslsocket = {
19✔
190
}
191
sslsocket.__index = sslsocket
19✔
192

193
local WAIT_FOR_READ =1
19✔
194
local WAIT_FOR_WRITE =2
19✔
195

196
function sslsocket.write(self, data, timeout)
19✔
197
    local start = clock.time()
16✔
198

199
    local size = #data
16✔
200
    local s = ffi.cast('const char *', data)
16✔
201

202
    local mode = WAIT_FOR_WRITE
16✔
203

204
    while true do
205
        local rc = nil
16✔
206
        if mode == WAIT_FOR_READ then
16✔
UNCOV
207
            rc = self.sock:readable(slice_wait(timeout, start))
×
208
        elseif mode == WAIT_FOR_WRITE then
16✔
209
            rc = self.sock:writable(slice_wait(timeout, start))
48✔
210
        else
UNCOV
211
            assert(false)
×
212
        end
213

214
        if not rc then
16✔
215
            self.sock._errno = errno.ETIMEDOUT
×
216
            return nil, 'Timeout exceeded'
×
217
        end
218

219
        ffi.C.ERR_clear_error()
16✔
220
        local num = ffi.C.SSL_write(self.ssl, s, size);
16✔
221
        if num <= 0 then
16✔
222
            local ssl_error = ffi.C.SSL_get_error(self.ssl, num);
×
223
            if ssl_error == SSL_ERROR_WANT_WRITE then
×
UNCOV
224
                mode = WAIT_FOR_WRITE
×
225
            elseif ssl_error == SSL_ERROR_WANT_READ then
×
226
                mode = WAIT_FOR_READ
×
227
            elseif ssl_error == SSL_ERROR_SYSCALL then
×
UNCOV
228
                return nil, self.sock:error()
×
UNCOV
229
            elseif ssl_error == SSL_ERROR_ZERO_RETURN then
×
UNCOV
230
                return 0
×
231
            else
UNCOV
232
                local error_string = ffi.string(ffi.C.ERR_error_string(ssl_error, nil))
×
UNCOV
233
                log.info(error_string)
×
UNCOV
234
                return nil, error_string
×
235
            end
236
        else
237
            return num
16✔
238
        end
239
    end
240
end
241

242
function sslsocket.close(self)
19✔
243
    return self.sock:close()
×
244
end
245

246
function sslsocket.error(self)
19✔
247
    local error_string =
UNCOV
248
        ffi.string(ffi.C.ERR_error_string(ffi.C.ERR_peek_last_error(), nil))
×
249

UNCOV
250
    return self.sock:error() or error_string
×
251
end
252

253
function sslsocket.errno(self)
19✔
UNCOV
254
    return self.sock:errno() or ffi.C.ERR_peek_last_error()
×
255
end
256

257
function sslsocket.fd(self)
19✔
UNCOV
258
    return self.sock:fd()
×
259
end
260

261
function sslsocket.nonblock(self, nb)
19✔
UNCOV
262
    return self.sock:nonblock(nb)
×
263
end
264

265
local function sysread(self, charptr, size, timeout)
266
    local start = clock.time()
27✔
267

268
    local mode = rawget(self, 'first_state') or WAIT_FOR_READ
27✔
269
    rawset(self, 'first_state', nil)
27✔
270

271
    while true do
272
        local rc = nil
48✔
273
        if mode == WAIT_FOR_READ then
48✔
274
            if ffi.C.SSL_pending(self.ssl) > 0 then
48✔
275
                rc = true
×
276
            else
277
                rc = self.sock:readable(slice_wait(timeout, start))
144✔
278
            end
279
        elseif mode == WAIT_FOR_WRITE then
×
280
            rc = self.sock:writable(slice_wait(timeout, start))
×
281
        else
UNCOV
282
            assert(false)
×
283
        end
284

285
        if not rc then
48✔
UNCOV
286
            self.sock._errno = errno.ETIMEDOUT
×
UNCOV
287
            return nil, 'Timeout exceeded'
×
288
        end
289

290
        ffi.C.ERR_clear_error()
48✔
291
        local num = ffi.C.SSL_read(self.ssl, charptr, size);
48✔
292
        if num <= 0 then
48✔
293
            local ssl_error = ffi.C.SSL_get_error(self.ssl, num);
32✔
294
            if ssl_error == SSL_ERROR_WANT_WRITE then
32✔
UNCOV
295
                mode = WAIT_FOR_WRITE
×
296
            elseif ssl_error == SSL_ERROR_WANT_READ then
32✔
297
                mode = WAIT_FOR_READ
21✔
298
            elseif ssl_error == SSL_ERROR_SYSCALL then
11✔
UNCOV
299
                return nil, self.sock:error()
×
300
            elseif ssl_error == SSL_ERROR_ZERO_RETURN then
11✔
UNCOV
301
                return 0
×
302
            else
303
                local error_string = ffi.string(ffi.C.ERR_error_string(ssl_error, nil))
11✔
304
                log.info(error_string)
11✔
305
                return nil, error_string
11✔
306
            end
307
        else
308
            return num
16✔
309
        end
310
    end
311
end
312

313
local function read(self, limit, timeout, check, ...)
314
    assert(limit >= 0)
27✔
315

316
    local start = clock.time()
27✔
317

318
    limit = math.min(limit, LIMIT_INFINITY)
27✔
319
    local rbuf = self.rbuf
27✔
320
    if rbuf == nil then
27✔
321
       rbuf = buffer.ibuf()
54✔
322
       rawset(self, 'rbuf', rbuf)
27✔
323
    end
324

325
    local len = check(self, limit, ...)
27✔
326
    if len ~= nil then
27✔
UNCOV
327
        local data = ffi.string(rbuf.rpos, len)
×
UNCOV
328
        rbuf.rpos = rbuf.rpos + len
×
UNCOV
329
        return data
×
330
    end
331

332
    while true do
333
        assert(rbuf:size() < limit)
54✔
334
        local to_read = math.min(limit - rbuf:size(), buffer.READAHEAD)
54✔
335
        local data = rbuf:reserve(to_read)
27✔
336
        assert(rbuf:unused() >= to_read)
54✔
337

338
        local res, err = sysread(self, data, rbuf:unused(), slice_wait(timeout, start))
81✔
339
        if res == 0 then -- EOF.
27✔
UNCOV
340
            local len = rbuf:size()
×
UNCOV
341
            local data = ffi.string(rbuf.rpos, len)
×
UNCOV
342
            rbuf.rpos = rbuf.rpos + len
×
UNCOV
343
            return data
×
344
        elseif res ~= nil then
27✔
345
            rbuf.wpos = rbuf.wpos + res
16✔
346
            local len = check(self, limit, ...)
16✔
347
            if len ~= nil then
16✔
348
                local data = ffi.string(rbuf.rpos, len)
16✔
349
                rbuf.rpos = rbuf.rpos + len
16✔
350
                return data
16✔
351
            end
352
        else
353
            return nil, err
11✔
354
        end
355
    end
356

357
    -- Not reached.
358
end
359

360
local function check_limit(self, limit)
UNCOV
361
    if self.rbuf:size() >= limit then
×
362
        return limit
×
363
    end
UNCOV
364
    return nil
×
365
end
366

367
local function check_delimiter(self, limit, eols)
368
    if limit == 0 then
43✔
UNCOV
369
        return 0
×
370
    end
371
    local rbuf = self.rbuf
43✔
372
    if rbuf:size() == 0 then
86✔
373
        return nil
27✔
374
    end
375

376
    local shortest
377
    for _, eol in ipairs(eols) do
48✔
378
        local data = ffi.C.memmem(rbuf.rpos, rbuf:size(), eol, #eol)
64✔
379
        if data ~= nil then
32✔
380
            local len = ffi.cast('char *', data) - rbuf.rpos + #eol
16✔
381
            if shortest == nil or shortest > len then
16✔
382
                shortest = len
16✔
383
            end
384
        end
385
    end
386
    if shortest ~= nil and shortest <= limit then
16✔
387
        return shortest
16✔
UNCOV
388
    elseif limit <= rbuf:size() then
×
UNCOV
389
        return limit
×
390
    end
UNCOV
391
    return nil
×
392
end
393

394
function sslsocket.read(self, opts, timeout)
19✔
395
    timeout = timeout or TIMEOUT_INFINITY
27✔
396
    if type(opts) == 'number' then
27✔
397
        return read(self, opts, timeout, check_limit)
×
398
    elseif type(opts) == 'string' then
27✔
399
        return read(self, LIMIT_INFINITY, timeout, check_delimiter, { opts })
×
400
    elseif type(opts) == 'table' then
27✔
401
        local chunk = opts.chunk or opts.size or LIMIT_INFINITY
27✔
402
        local delimiter = opts.delimiter or opts.line
27✔
403
        if delimiter == nil then
27✔
404
            return read(self, chunk, timeout, check_limit)
×
405
        elseif type(delimiter) == 'string' then
27✔
UNCOV
406
            return read(self, chunk, timeout, check_delimiter, { delimiter })
×
407
        elseif type(delimiter) == 'table' then
27✔
408
            return read(self, chunk, timeout, check_delimiter, delimiter)
27✔
409
        end
410
    end
UNCOV
411
    error('Usage: s:read(delimiter|chunk|{delimiter = x, chunk = x}, timeout)')
×
412
end
413

414
function sslsocket.readable(self, timeout)
19✔
UNCOV
415
    return self.sock:readable(timeout)
×
416
end
417

418
local function wrap_accepted_socket(sock, sslctx)
419
    sslctx = sslctx or default_ctx
27✔
420

421
    ffi.C.ERR_clear_error()
27✔
422
    local ssl = ffi.gc(ffi.C.SSL_new(sslctx),
54✔
423
                       ffi.C.SSL_free)
27✔
424
    if ssl == nil then
27✔
UNCOV
425
        sock:close()
×
UNCOV
426
        return nil, 'SSL_new failed'
×
427
    end
428

429
    sock:nonblock(true)
27✔
430

431
    ffi.C.ERR_clear_error()
27✔
432
    local rc = ffi.C.SSL_set_fd(ssl, sock:fd())
54✔
433
    if rc == 0 then
27✔
UNCOV
434
        sock:close()
×
UNCOV
435
        return nil, 'SSL_set_fd failed'
×
436
    end
437

438
    ffi.C.ERR_clear_error()
27✔
439
    ffi.C.SSL_set_accept_state(ssl);
27✔
440

441
    local self = setmetatable({}, sslsocket)
27✔
442
    rawset(self, 'sock', sock)
27✔
443
    rawset(self, 'ctx', sslctx)
27✔
444
    rawset(self, 'ssl', ssl)
27✔
445
    return self
27✔
446
end
447

448
local function tcp_server(host, port, handler, timeout, sslctx)
449
    sslctx = sslctx or default_ctx
28✔
450

451
    local handler_function = handler.handler
28✔
452

453
    local wrapper = function(sock, from)
454
        local self, err = wrap_accepted_socket(sock, sslctx)
27✔
455
        if not self then
27✔
UNCOV
456
            log.info('sslsocket.tcp_server error: %s ', err)
×
457
        else
458
            handler_function(self, from)
27✔
459
        end
460
    end
461

462
    handler.handler = wrapper
28✔
463

464
    return socket.tcp_server(host, port, handler, timeout)
28✔
465
end
466

467
return {
19✔
468
    tls_server_method = tls_server_method,
19✔
469

470
    ctx = ctx,
19✔
471
    ctx_use_private_key_file = ctx_use_private_key_file,
19✔
472
    ctx_use_certificate_file = ctx_use_certificate_file,
19✔
473
    ctx_load_verify_locations = ctx_load_verify_locations,
19✔
474
    ctx_set_cipher_list = ctx_set_cipher_list,
19✔
475
    ctx_set_verify = ctx_set_verify,
19✔
476

477
    tcp_server = tcp_server,
19✔
478

479
    wrap_accepted_socket = wrap_accepted_socket,
19✔
480
}
19✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc