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

tarantool / http / 11743252162

08 Nov 2024 01:42PM UTC coverage: 78.641% (+0.3%) from 78.344%
11743252162

push

github

web-flow
Merge c2fe1530d into 0e7af5a7d

278 of 349 new or added lines in 2 files covered. (79.66%)

1 existing line in 1 file now uncovered.

891 of 1133 relevant lines covered (78.64%)

71.02 hits per line

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

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

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

12
pcall(
2✔
13
    function()
14
        ffi.cdef[[
1✔
15
            typedef struct SSL_METHOD {} SSL_METHOD;
16
            typedef struct SSL_CTX {} SSL_CTX;
17
            typedef struct SSL {} SSL;
18

19
            const SSL_METHOD *TLS_server_method(void);
20
            const SSL_METHOD *TLS_client_method(void);
21

22
            SSL_CTX *SSL_CTX_new(const SSL_METHOD *method);
23
            void SSL_CTX_free(SSL_CTX *);
24

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

30
            void SSL_CTX_set_default_passwd_cb(SSL_CTX *ctx, pem_passwd_cb cb);
31

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

38
            SSL *SSL_new(SSL_CTX *ctx);
39
            void SSL_free(SSL *ssl);
40

41
            int SSL_set_fd(SSL *s, int fd);
42

43
            void SSL_set_connect_state(SSL *s);
44
            void SSL_set_accept_state(SSL *s);
45

46
            int SSL_write(SSL *ssl, const void *buf, int num);
47
            int SSL_read(SSL *ssl, void *buf, int num);
48

49
            int SSL_pending(const SSL *ssl);
50

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

55
            int SSL_get_error(const SSL *s, int ret_code);
56

57
            typedef socklen_t uint32;
58
            int getsockopt(int sockfd, int level, int optname, void *optval,
59
                            socklen_t *optlen);
60

61
            int setsockopt(int sockfd, int level, int optname,
62
                            const void *optval, socklen_t optlen);
63

64
            void *memmem(const void *haystack, size_t haystacklen,
65
                    const void *needle, size_t needlelen);
66
        ]]
1✔
67
    end)
68

69
local function slice_wait(timeout, starttime)
70
    if timeout == nil then
30✔
71
        return nil
6✔
72
    end
73

74
    return timeout - (clock.time() - starttime)
24✔
75
end
76

77
local X509_FILETYPE_PEM       = 1
1✔
78

79
local function ctx(method)
80
    ffi.C.ERR_clear_error()
16✔
81
    local newctx =
82
        ffi.gc(ffi.C.SSL_CTX_new(method), ffi.C.SSL_CTX_free)
16✔
83

84
    return newctx
16✔
85
end
86

87
local function ctx_use_private_key_file(ctx, pem_file, password, password_file)
88
    ffi.C.SSL_CTX_set_default_passwd_cb(ctx, box.NULL);
15✔
89

90
    if password ~= nil then
15✔
91
        log.info('set private key password')
2✔
92

93
        ffi.C.SSL_CTX_set_default_passwd_cb_userdata(ctx,
4✔
94
            ffi.cast('void*', ffi.cast('char*', password)))
2✔
95
        local rc = ffi.C.SSL_CTX_use_PrivateKey_file(ctx, pem_file, X509_FILETYPE_PEM)
2✔
96
        ffi.C.SSL_CTX_set_default_passwd_cb_userdata(ctx, box.NULL);
2✔
97

98
        if rc == 1 then
2✔
99
            return true
1✔
100
        end
101
    end
102

103
    if password_file ~= nil then
14✔
104
        local fh = fio.open(password_file, {'O_RDONLY'})
2✔
105
        if fh == nil then
2✔
NEW
106
            ffi.C.ERR_clear_error()
×
NEW
107
            return false
×
108
        end
109

110
        local is_loaded = false
2✔
111
        local password_from_file = ""
2✔
112
        local char
113
        while char ~= '' do
3✔
114
            while true do
115
                char = fh:read(1)
66✔
116
                if char == '\n' or char == '' then
33✔
117
                    break
118
                end
119
                password_from_file = password_from_file .. char
30✔
120
            end
121

122
            ffi.C.SSL_CTX_set_default_passwd_cb_userdata(ctx,
6✔
123
                ffi.cast('void*', ffi.cast('char*', password_from_file)))
3✔
124
            local rc = ffi.C.SSL_CTX_use_PrivateKey_file(ctx, pem_file, X509_FILETYPE_PEM)
3✔
125
            ffi.C.SSL_CTX_set_default_passwd_cb_userdata(ctx, box.NULL);
3✔
126

127
            if rc == 1 then
3✔
128
                is_loaded = true
2✔
129
                break
2✔
130
            end
131

132
            ffi.C.ERR_clear_error()
1✔
133
            password_from_file = ''
1✔
134
        end
135

136
        fh:close()
2✔
137

138
        if is_loaded == true then
2✔
139
            return true
2✔
140
        else
NEW
141
            return false
×
142
        end
143
    end
144

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

151
    return true
9✔
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
12✔
156
        ffi.C.ERR_clear_error()
1✔
157
        return false
1✔
158
    end
159
    return true
11✔
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
7✔
164
        ffi.C.ERR_clear_error()
1✔
165
        return false
1✔
166
    end
167
    return true
6✔
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, flags)
178
    ffi.C.SSL_CTX_set_verify(ctx, flags, box.NULL)
6✔
179
end
180

181
local default_ctx = ctx(ffi.C.TLS_server_method())
1✔
182

183
local SSL_ERROR_WANT_READ             =2
1✔
184
local SSL_ERROR_WANT_WRITE            =3
1✔
185
local SSL_ERROR_SYSCALL               =5 -- look at error stack/return value/errno
1✔
186
local SSL_ERROR_ZERO_RETURN           =6
1✔
187

188
local sslsocket = {
1✔
189
}
190
sslsocket.__index = sslsocket
1✔
191

192
local WAIT_FOR_READ =1
1✔
193
local WAIT_FOR_WRITE =2
1✔
194

195
function sslsocket.write(self, data, timeout)
1✔
196
    local start = clock.time()
6✔
197

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

201
    local mode = WAIT_FOR_WRITE
6✔
202

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

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

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

241
function sslsocket.close(self)
1✔
NEW
242
    return self.sock:close()
×
243
end
244

245
function sslsocket.error(self)
1✔
246
    local error_string =
NEW
247
        ffi.string(ffi.C.ERR_error_string(ffi.C.ERR_peek_last_error(), nil))
×
248

NEW
249
    return self.sock:error() or error_string
×
250
end
251

252
function sslsocket.errno(self)
1✔
NEW
253
    return self.sock:errno() or ffi.C.ERR_peek_last_error()
×
254
end
255

256
function sslsocket.setsockopt(self, level, name, value)
1✔
NEW
257
    return self.sock:setsockopt(level, name, value)
×
258
end
259

260
function sslsocket.getsockopt(self, level, name)
1✔
NEW
261
    return self.sock:getsockopt(level, name)
×
262
end
263

264
function sslsocket.name(self)
1✔
NEW
265
    return self.sock:name()
×
266
end
267

268
function sslsocket.peer(self)
1✔
NEW
269
    return self.sock:peer()
×
270
end
271

272
function sslsocket.fd(self)
1✔
NEW
273
    return self.sock:fd()
×
274
end
275

276
function sslsocket.nonblock(self, nb)
1✔
NEW
277
    return self.sock:nonblock(nb)
×
278
end
279

280
local function sysread(self, charptr, size, timeout)
281
    local start = clock.time()
9✔
282

283
    local mode = rawget(self, 'first_state') or WAIT_FOR_READ
9✔
284
    rawset(self, 'first_state', nil)
9✔
285

286
    while true do
287
        local rc = nil
15✔
288
        if mode == WAIT_FOR_READ then
15✔
289
            if ffi.C.SSL_pending(self.ssl) > 0 then
15✔
NEW
290
                rc = true
×
291
            else
292
                rc = self.sock:readable(slice_wait(timeout, start))
45✔
293
            end
NEW
294
        elseif mode == WAIT_FOR_WRITE then
×
NEW
295
            rc = self.sock:writable(slice_wait(timeout, start))
×
296
        else
NEW
297
            assert(false)
×
298
        end
299

300
        if not rc then
15✔
NEW
301
            self.sock._errno = errno.ETIMEDOUT
×
NEW
302
            return nil, 'Timeout exceeded'
×
303
        end
304

305
        ffi.C.ERR_clear_error()
15✔
306
        local num = ffi.C.SSL_read(self.ssl, charptr, size);
15✔
307
        if num <= 0 then
15✔
308
            local ssl_error = ffi.C.SSL_get_error(self.ssl, num);
9✔
309
            if ssl_error == SSL_ERROR_WANT_WRITE then
9✔
NEW
310
                mode = WAIT_FOR_WRITE
×
311
            elseif ssl_error == SSL_ERROR_WANT_READ then
9✔
312
                mode = WAIT_FOR_READ
6✔
313
            elseif ssl_error == SSL_ERROR_SYSCALL then
3✔
314
                return nil, self.sock:error()
6✔
NEW
315
            elseif ssl_error == SSL_ERROR_ZERO_RETURN then
×
NEW
316
                return 0
×
317
            else
NEW
318
                local error_string = ffi.string(ffi.C.ERR_error_string(ssl_error, nil))
×
NEW
319
                log.info(error_string)
×
NEW
320
                return nil, error_string
×
321
            end
322
        else
323
            return num
6✔
324
        end
325
    end
326
end
327

328
local function recv(self, limit)
NEW
329
    local buffer = ffi.new('char[?]', limit)
×
NEW
330
    local readsize = sysread(self, buffer, limit, 60)
×
NEW
331
    return ffi.string(buffer, readsize)
×
332
end
333

334
local function read(self, limit, timeout, check, ...)
335
    assert(limit >= 0)
9✔
336

337
    local start = clock.time()
9✔
338

339
    limit = math.min(limit, LIMIT_INFINITY)
9✔
340
    local rbuf = self.rbuf
9✔
341
    if rbuf == nil then
9✔
342
       rbuf = buffer.ibuf()
18✔
343
       rawset(self, 'rbuf', rbuf)
9✔
344
    end
345

346
    local len = check(self, limit, ...)
9✔
347
    if len ~= nil then
9✔
NEW
348
        local data = ffi.string(rbuf.rpos, len)
×
NEW
349
        rbuf.rpos = rbuf.rpos + len
×
NEW
350
        return data
×
351
    end
352

353
    while true do
354
        assert(rbuf:size() < limit)
18✔
355
        local to_read = math.min(limit - rbuf:size(), buffer.READAHEAD)
18✔
356
        local data = rbuf:reserve(to_read)
9✔
357
        assert(rbuf:unused() >= to_read)
18✔
358

359
        local res, err = sysread(self, data, rbuf:unused(), slice_wait(timeout, start))
27✔
360
        if res == 0 then -- eof
9✔
NEW
361
            local len = rbuf:size()
×
NEW
362
            local data = ffi.string(rbuf.rpos, len)
×
NEW
363
            rbuf.rpos = rbuf.rpos + len
×
NEW
364
            return data
×
365
        elseif res ~= nil then
9✔
366
            rbuf.wpos = rbuf.wpos + res
6✔
367
            local len = check(self, limit, ...)
6✔
368
            if len ~= nil then
6✔
369
                local data = ffi.string(rbuf.rpos, len)
6✔
370
                rbuf.rpos = rbuf.rpos + len
6✔
371
                return data
6✔
372
            end
373
        else
374
            return nil, err
3✔
375
        end
376
    end
377

378
    -- not reached
379
end
380

381
local function check_limit(self, limit)
NEW
382
    if self.rbuf:size() >= limit then
×
NEW
383
        return limit
×
384
    end
NEW
385
    return nil
×
386
end
387

388
local function check_delimiter(self, limit, eols)
389
    if limit == 0 then
15✔
NEW
390
        return 0
×
391
    end
392
    local rbuf = self.rbuf
15✔
393
    if rbuf:size() == 0 then
30✔
394
        return nil
9✔
395
    end
396

397
    local shortest
398
    for _, eol in ipairs(eols) do
18✔
399
        local data = ffi.C.memmem(rbuf.rpos, rbuf:size(), eol, #eol)
24✔
400
        if data ~= nil then
12✔
401
            local len = ffi.cast('char *', data) - rbuf.rpos + #eol
6✔
402
            if shortest == nil or shortest > len then
6✔
403
                shortest = len
6✔
404
            end
405
        end
406
    end
407
    if shortest ~= nil and shortest <= limit then
6✔
408
        return shortest
6✔
NEW
409
    elseif limit <= rbuf:size() then
×
NEW
410
        return limit
×
411
    end
NEW
412
    return nil
×
413
end
414

415
function sslsocket.recv(self, limit)
1✔
NEW
416
    return recv(self, limit)
×
417
end
418

419
function sslsocket.read(self, opts, timeout)
1✔
420
    timeout = timeout or TIMEOUT_INFINITY
9✔
421
    if type(opts) == 'number' then
9✔
NEW
422
        return read(self, opts, timeout, check_limit)
×
423
    elseif type(opts) == 'string' then
9✔
NEW
424
        return read(self, LIMIT_INFINITY, timeout, check_delimiter, { opts })
×
425
    elseif type(opts) == 'table' then
9✔
426
        local chunk = opts.chunk or opts.size or LIMIT_INFINITY
9✔
427
        local delimiter = opts.delimiter or opts.line
9✔
428
        if delimiter == nil then
9✔
NEW
429
            return read(self, chunk, timeout, check_limit)
×
430
        elseif type(delimiter) == 'string' then
9✔
NEW
431
            return read(self, chunk, timeout, check_delimiter, { delimiter })
×
432
        elseif type(delimiter) == 'table' then
9✔
433
            return read(self, chunk, timeout, check_delimiter, delimiter)
9✔
434
        end
435
    end
NEW
436
    error('Usage: s:read(delimiter|chunk|{delimiter = x, chunk = x}, timeout)')
×
437
end
438

439
function sslsocket.readable(self, timeout)
1✔
NEW
440
    return self.sock:readable(timeout)
×
441
end
442

443
local function wrap_accepted_socket(sock, sslctx)
444
    sslctx = sslctx or default_ctx
9✔
445

446
    ffi.C.ERR_clear_error()
9✔
447
    local ssl = ffi.gc(ffi.C.SSL_new(sslctx),
18✔
448
                       ffi.C.SSL_free)
9✔
449
    if ssl == nil then
9✔
NEW
450
        sock:close()
×
NEW
451
        return nil, 'SSL_new failed'
×
452
    end
453

454
    sock:nonblock(true)
9✔
455

456
    ffi.C.ERR_clear_error()
9✔
457
    local rc = ffi.C.SSL_set_fd(ssl, sock:fd())
18✔
458
    if rc == 0 then
9✔
NEW
459
        sock:close()
×
NEW
460
        return nil, 'SSL_set_fd failed'
×
461
    end
462

463
    ffi.C.ERR_clear_error()
9✔
464
    ffi.C.SSL_set_accept_state(ssl);
9✔
465

466
    local self = setmetatable({}, sslsocket)
9✔
467
    rawset(self, 'sock', sock)
9✔
468
    rawset(self, 'ctx', sslctx)
9✔
469
    rawset(self, 'ssl', ssl)
9✔
470
    return self
9✔
471
end
472

473
local function tcp_server(host, port, handler, timeout, sslctx)
474
    sslctx = sslctx or default_ctx
9✔
475

476
    local handler_function = handler.handler
9✔
477

478
    local wrapper = function (sock, from)
479
        local self, err = wrap_accepted_socket(sock, sslctx)
9✔
480
        if not self then
9✔
NEW
481
            log.info('sslsocket.tcp_server error: %s ', err)
×
482
        else
483
            handler_function(self, from)
9✔
484
        end
485
    end
486

487
    handler.handler = wrapper
9✔
488

489
    return socket.tcp_server(host, port, handler, timeout)
9✔
490
end
491

492
return {
1✔
493
    ctx = ctx,
1✔
494
    ctx_use_private_key_file = ctx_use_private_key_file,
1✔
495
    ctx_use_certificate_file = ctx_use_certificate_file,
1✔
496
    ctx_load_verify_locations = ctx_load_verify_locations,
1✔
497
    ctx_set_cipher_list = ctx_set_cipher_list,
1✔
498
    ctx_set_verify = ctx_set_verify,
1✔
499

500
    tcp_server = tcp_server,
1✔
501

502
    wrap_accepted_socket = wrap_accepted_socket,
1✔
503
}
1✔
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

© 2025 Coveralls, Inc