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

tarantool / http / 11743392950

08 Nov 2024 01:52PM UTC coverage: 79.107% (+0.8%) from 78.344%
11743392950

push

github

web-flow
Merge 47576bf4e into 0e7af5a7d

273 of 336 new or added lines in 2 files covered. (81.25%)

1 existing line in 1 file now uncovered.

886 of 1120 relevant lines covered (79.11%)

71.86 hits per line

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

75.59
/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
            void *memmem(const void *haystack, size_t haystacklen,
58
                    const void *needle, size_t needlelen);
59
        ]]
1✔
60
    end)
61

62
local function slice_wait(timeout, starttime)
63
    if timeout == nil then
30✔
64
        return nil
6✔
65
    end
66

67
    return timeout - (clock.time() - starttime)
24✔
68
end
69

70
local X509_FILETYPE_PEM       = 1
1✔
71

72
local function ctx(method)
73
    ffi.C.ERR_clear_error()
16✔
74
    local newctx =
75
        ffi.gc(ffi.C.SSL_CTX_new(method), ffi.C.SSL_CTX_free)
16✔
76

77
    return newctx
16✔
78
end
79

80
local function ctx_use_private_key_file(ctx, pem_file, password, password_file)
81
    ffi.C.SSL_CTX_set_default_passwd_cb(ctx, box.NULL);
15✔
82

83
    if password ~= nil then
15✔
84
        log.info('set private key password')
2✔
85

86
        ffi.C.SSL_CTX_set_default_passwd_cb_userdata(ctx,
4✔
87
            ffi.cast('void*', ffi.cast('char*', password)))
2✔
88
        local rc = ffi.C.SSL_CTX_use_PrivateKey_file(ctx, pem_file, X509_FILETYPE_PEM)
2✔
89
        ffi.C.SSL_CTX_set_default_passwd_cb_userdata(ctx, box.NULL);
2✔
90

91
        if rc == 1 then
2✔
92
            return true
1✔
93
        end
94
    end
95

96
    if password_file ~= nil then
14✔
97
        local fh = fio.open(password_file, {'O_RDONLY'})
2✔
98
        if fh == nil then
2✔
NEW
99
            ffi.C.ERR_clear_error()
×
NEW
100
            return false
×
101
        end
102

103
        local is_loaded = false
2✔
104
        local password_from_file = ""
2✔
105
        local char
106
        while char ~= '' do
3✔
107
            while true do
108
                char = fh:read(1)
66✔
109
                if char == '\n' or char == '' then
33✔
110
                    break
111
                end
112
                password_from_file = password_from_file .. char
30✔
113
            end
114

115
            ffi.C.SSL_CTX_set_default_passwd_cb_userdata(ctx,
6✔
116
                ffi.cast('void*', ffi.cast('char*', password_from_file)))
3✔
117
            local rc = ffi.C.SSL_CTX_use_PrivateKey_file(ctx, pem_file, X509_FILETYPE_PEM)
3✔
118
            ffi.C.SSL_CTX_set_default_passwd_cb_userdata(ctx, box.NULL);
3✔
119

120
            if rc == 1 then
3✔
121
                is_loaded = true
2✔
122
                break
2✔
123
            end
124

125
            ffi.C.ERR_clear_error()
1✔
126
            password_from_file = ''
1✔
127
        end
128

129
        fh:close()
2✔
130

131
        if is_loaded == true then
2✔
132
            return true
2✔
133
        else
NEW
134
            return false
×
135
        end
136
    end
137

138
    local rc = ffi.C.SSL_CTX_use_PrivateKey_file(ctx, pem_file, X509_FILETYPE_PEM)
12✔
139
    if rc ~= 1 then
12✔
140
        ffi.C.ERR_clear_error()
3✔
141
        return false
3✔
142
    end
143

144
    return true
9✔
145
end
146

147
local function ctx_use_certificate_file(ctx, pem_file)
148
    if ffi.C.SSL_CTX_use_certificate_file(ctx, pem_file, X509_FILETYPE_PEM) ~= 1 then
12✔
149
        ffi.C.ERR_clear_error()
1✔
150
        return false
1✔
151
    end
152
    return true
11✔
153
end
154

155
local function ctx_load_verify_locations(ctx, ca_file)
156
    if ffi.C.SSL_CTX_load_verify_locations(ctx, ca_file, box.NULL) ~= 1 then
7✔
157
        ffi.C.ERR_clear_error()
1✔
158
        return false
1✔
159
    end
160
    return true
6✔
161
end
162

163
local function ctx_set_cipher_list(ctx, str)
164
    if ffi.C.SSL_CTX_set_cipher_list(ctx, str) ~= 1 then
2✔
165
        return false
1✔
166
    end
167
    return true
1✔
168
end
169

170
local function ctx_set_verify(ctx, flags)
171
    ffi.C.SSL_CTX_set_verify(ctx, flags, box.NULL)
6✔
172
end
173

174
local default_ctx = ctx(ffi.C.TLS_server_method())
1✔
175

176
local SSL_ERROR_WANT_READ             =2
1✔
177
local SSL_ERROR_WANT_WRITE            =3
1✔
178
local SSL_ERROR_SYSCALL               =5 -- look at error stack/return value/errno
1✔
179
local SSL_ERROR_ZERO_RETURN           =6
1✔
180

181
local sslsocket = {
1✔
182
}
183
sslsocket.__index = sslsocket
1✔
184

185
local WAIT_FOR_READ =1
1✔
186
local WAIT_FOR_WRITE =2
1✔
187

188
function sslsocket.write(self, data, timeout)
1✔
189
    local start = clock.time()
6✔
190

191
    local size = #data
6✔
192
    local s = ffi.cast('const char *', data)
6✔
193

194
    local mode = WAIT_FOR_WRITE
6✔
195

196
    while true do
197
        local rc = nil
6✔
198
        if mode == WAIT_FOR_READ then
6✔
NEW
199
            rc = self.sock:readable(slice_wait(timeout, start))
×
200
        elseif mode == WAIT_FOR_WRITE then
6✔
201
            rc = self.sock:writable(slice_wait(timeout, start))
18✔
202
        else
NEW
203
            assert(false)
×
204
        end
205

206
        if not rc then
6✔
NEW
207
            self.sock._errno = errno.ETIMEDOUT
×
NEW
208
            return nil, 'Timeout exceeded'
×
209
        end
210

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

234
function sslsocket.close(self)
1✔
NEW
235
    return self.sock:close()
×
236
end
237

238
function sslsocket.error(self)
1✔
239
    local error_string =
NEW
240
        ffi.string(ffi.C.ERR_error_string(ffi.C.ERR_peek_last_error(), nil))
×
241

NEW
242
    return self.sock:error() or error_string
×
243
end
244

245
function sslsocket.errno(self)
1✔
NEW
246
    return self.sock:errno() or ffi.C.ERR_peek_last_error()
×
247
end
248

249
function sslsocket.fd(self)
1✔
NEW
250
    return self.sock:fd()
×
251
end
252

253
function sslsocket.nonblock(self, nb)
1✔
NEW
254
    return self.sock:nonblock(nb)
×
255
end
256

257
local function sysread(self, charptr, size, timeout)
258
    local start = clock.time()
9✔
259

260
    local mode = rawget(self, 'first_state') or WAIT_FOR_READ
9✔
261
    rawset(self, 'first_state', nil)
9✔
262

263
    while true do
264
        local rc = nil
15✔
265
        if mode == WAIT_FOR_READ then
15✔
266
            if ffi.C.SSL_pending(self.ssl) > 0 then
15✔
NEW
267
                rc = true
×
268
            else
269
                rc = self.sock:readable(slice_wait(timeout, start))
45✔
270
            end
NEW
271
        elseif mode == WAIT_FOR_WRITE then
×
NEW
272
            rc = self.sock:writable(slice_wait(timeout, start))
×
273
        else
NEW
274
            assert(false)
×
275
        end
276

277
        if not rc then
15✔
NEW
278
            self.sock._errno = errno.ETIMEDOUT
×
NEW
279
            return nil, 'Timeout exceeded'
×
280
        end
281

282
        ffi.C.ERR_clear_error()
15✔
283
        local num = ffi.C.SSL_read(self.ssl, charptr, size);
15✔
284
        if num <= 0 then
15✔
285
            local ssl_error = ffi.C.SSL_get_error(self.ssl, num);
9✔
286
            if ssl_error == SSL_ERROR_WANT_WRITE then
9✔
NEW
287
                mode = WAIT_FOR_WRITE
×
288
            elseif ssl_error == SSL_ERROR_WANT_READ then
9✔
289
                mode = WAIT_FOR_READ
6✔
290
            elseif ssl_error == SSL_ERROR_SYSCALL then
3✔
291
                return nil, self.sock:error()
6✔
NEW
292
            elseif ssl_error == SSL_ERROR_ZERO_RETURN then
×
NEW
293
                return 0
×
294
            else
NEW
295
                local error_string = ffi.string(ffi.C.ERR_error_string(ssl_error, nil))
×
NEW
296
                log.info(error_string)
×
NEW
297
                return nil, error_string
×
298
            end
299
        else
300
            return num
6✔
301
        end
302
    end
303
end
304

305
local function read(self, limit, timeout, check, ...)
306
    assert(limit >= 0)
9✔
307

308
    local start = clock.time()
9✔
309

310
    limit = math.min(limit, LIMIT_INFINITY)
9✔
311
    local rbuf = self.rbuf
9✔
312
    if rbuf == nil then
9✔
313
       rbuf = buffer.ibuf()
18✔
314
       rawset(self, 'rbuf', rbuf)
9✔
315
    end
316

317
    local len = check(self, limit, ...)
9✔
318
    if len ~= nil then
9✔
NEW
319
        local data = ffi.string(rbuf.rpos, len)
×
NEW
320
        rbuf.rpos = rbuf.rpos + len
×
NEW
321
        return data
×
322
    end
323

324
    while true do
325
        assert(rbuf:size() < limit)
18✔
326
        local to_read = math.min(limit - rbuf:size(), buffer.READAHEAD)
18✔
327
        local data = rbuf:reserve(to_read)
9✔
328
        assert(rbuf:unused() >= to_read)
18✔
329

330
        local res, err = sysread(self, data, rbuf:unused(), slice_wait(timeout, start))
27✔
331
        if res == 0 then -- eof
9✔
NEW
332
            local len = rbuf:size()
×
NEW
333
            local data = ffi.string(rbuf.rpos, len)
×
NEW
334
            rbuf.rpos = rbuf.rpos + len
×
NEW
335
            return data
×
336
        elseif res ~= nil then
9✔
337
            rbuf.wpos = rbuf.wpos + res
6✔
338
            local len = check(self, limit, ...)
6✔
339
            if len ~= nil then
6✔
340
                local data = ffi.string(rbuf.rpos, len)
6✔
341
                rbuf.rpos = rbuf.rpos + len
6✔
342
                return data
6✔
343
            end
344
        else
345
            return nil, err
3✔
346
        end
347
    end
348

349
    -- not reached
350
end
351

352
local function check_limit(self, limit)
NEW
353
    if self.rbuf:size() >= limit then
×
NEW
354
        return limit
×
355
    end
NEW
356
    return nil
×
357
end
358

359
local function check_delimiter(self, limit, eols)
360
    if limit == 0 then
15✔
NEW
361
        return 0
×
362
    end
363
    local rbuf = self.rbuf
15✔
364
    if rbuf:size() == 0 then
30✔
365
        return nil
9✔
366
    end
367

368
    local shortest
369
    for _, eol in ipairs(eols) do
18✔
370
        local data = ffi.C.memmem(rbuf.rpos, rbuf:size(), eol, #eol)
24✔
371
        if data ~= nil then
12✔
372
            local len = ffi.cast('char *', data) - rbuf.rpos + #eol
6✔
373
            if shortest == nil or shortest > len then
6✔
374
                shortest = len
6✔
375
            end
376
        end
377
    end
378
    if shortest ~= nil and shortest <= limit then
6✔
379
        return shortest
6✔
NEW
380
    elseif limit <= rbuf:size() then
×
NEW
381
        return limit
×
382
    end
NEW
383
    return nil
×
384
end
385

386
function sslsocket.read(self, opts, timeout)
1✔
387
    timeout = timeout or TIMEOUT_INFINITY
9✔
388
    if type(opts) == 'number' then
9✔
NEW
389
        return read(self, opts, timeout, check_limit)
×
390
    elseif type(opts) == 'string' then
9✔
NEW
391
        return read(self, LIMIT_INFINITY, timeout, check_delimiter, { opts })
×
392
    elseif type(opts) == 'table' then
9✔
393
        local chunk = opts.chunk or opts.size or LIMIT_INFINITY
9✔
394
        local delimiter = opts.delimiter or opts.line
9✔
395
        if delimiter == nil then
9✔
NEW
396
            return read(self, chunk, timeout, check_limit)
×
397
        elseif type(delimiter) == 'string' then
9✔
NEW
398
            return read(self, chunk, timeout, check_delimiter, { delimiter })
×
399
        elseif type(delimiter) == 'table' then
9✔
400
            return read(self, chunk, timeout, check_delimiter, delimiter)
9✔
401
        end
402
    end
NEW
403
    error('Usage: s:read(delimiter|chunk|{delimiter = x, chunk = x}, timeout)')
×
404
end
405

406
function sslsocket.readable(self, timeout)
1✔
NEW
407
    return self.sock:readable(timeout)
×
408
end
409

410
local function wrap_accepted_socket(sock, sslctx)
411
    sslctx = sslctx or default_ctx
9✔
412

413
    ffi.C.ERR_clear_error()
9✔
414
    local ssl = ffi.gc(ffi.C.SSL_new(sslctx),
18✔
415
                       ffi.C.SSL_free)
9✔
416
    if ssl == nil then
9✔
NEW
417
        sock:close()
×
NEW
418
        return nil, 'SSL_new failed'
×
419
    end
420

421
    sock:nonblock(true)
9✔
422

423
    ffi.C.ERR_clear_error()
9✔
424
    local rc = ffi.C.SSL_set_fd(ssl, sock:fd())
18✔
425
    if rc == 0 then
9✔
NEW
426
        sock:close()
×
NEW
427
        return nil, 'SSL_set_fd failed'
×
428
    end
429

430
    ffi.C.ERR_clear_error()
9✔
431
    ffi.C.SSL_set_accept_state(ssl);
9✔
432

433
    local self = setmetatable({}, sslsocket)
9✔
434
    rawset(self, 'sock', sock)
9✔
435
    rawset(self, 'ctx', sslctx)
9✔
436
    rawset(self, 'ssl', ssl)
9✔
437
    return self
9✔
438
end
439

440
local function tcp_server(host, port, handler, timeout, sslctx)
441
    sslctx = sslctx or default_ctx
9✔
442

443
    local handler_function = handler.handler
9✔
444

445
    local wrapper = function (sock, from)
446
        local self, err = wrap_accepted_socket(sock, sslctx)
9✔
447
        if not self then
9✔
NEW
448
            log.info('sslsocket.tcp_server error: %s ', err)
×
449
        else
450
            handler_function(self, from)
9✔
451
        end
452
    end
453

454
    handler.handler = wrapper
9✔
455

456
    return socket.tcp_server(host, port, handler, timeout)
9✔
457
end
458

459
return {
1✔
460
    ctx = ctx,
1✔
461
    ctx_use_private_key_file = ctx_use_private_key_file,
1✔
462
    ctx_use_certificate_file = ctx_use_certificate_file,
1✔
463
    ctx_load_verify_locations = ctx_load_verify_locations,
1✔
464
    ctx_set_cipher_list = ctx_set_cipher_list,
1✔
465
    ctx_set_verify = ctx_set_verify,
1✔
466

467
    tcp_server = tcp_server,
1✔
468

469
    wrap_accepted_socket = wrap_accepted_socket,
1✔
470
}
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