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

tarantool / http / 14176440247

31 Mar 2025 04:24PM UTC coverage: 78.708% (+0.3%) from 78.435%
14176440247

push

github

web-flow
Merge 790f3fd5d into c035e19c5

865 of 1099 relevant lines covered (78.71%)

72.82 hits per line

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

76.68
/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 tls_server_method()
73
    return ffi.C.TLS_server_method()
13✔
74
end
75

76
local function ctx(method)
77
    ffi.C.ERR_clear_error()
14✔
78

79
    return ffi.gc(ffi.C.SSL_CTX_new(method), ffi.C.SSL_CTX_free)
14✔
80
end
81

82
local function ctx_use_private_key_file(ctx, pem_file, password, password_file)
83
    ffi.C.SSL_CTX_set_default_passwd_cb(ctx, box.NULL);
13✔
84

85
    if password ~= nil then
13✔
86
        log.info('set private key password')
2✔
87

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

93
        if rc == 1 then
2✔
94
            return true
1✔
95
        end
96
        ffi.C.ERR_clear_error()
1✔
97
    end
98

99
    if password_file ~= nil then
12✔
100
        local fh = fio.open(password_file, {'O_RDONLY'})
2✔
101
        if fh == nil then
2✔
102
            return false
×
103
        end
104

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

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

122
            if rc == 1 then
3✔
123
                is_loaded = true
2✔
124
                break
2✔
125
            end
126

127
            ffi.C.ERR_clear_error()
1✔
128
            password_from_file = ''
1✔
129
        end
130

131
        fh:close()
2✔
132

133
        return is_loaded
2✔
134
    end
135

136
    local rc = ffi.C.SSL_CTX_use_PrivateKey_file(ctx, pem_file, X509_FILETYPE_PEM)
10✔
137
    if rc ~= 1 then
10✔
138
        ffi.C.ERR_clear_error()
2✔
139
        return false
2✔
140
    end
141

142
    return true
8✔
143
end
144

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

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

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

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

172
local default_ctx = ctx(ffi.C.TLS_server_method())
1✔
173

174
local SSL_ERROR_WANT_READ   = 2
1✔
175
local SSL_ERROR_WANT_WRITE  = 3
1✔
176
local SSL_ERROR_SYSCALL     = 5 -- Look at error stack/return value/errno.
1✔
177
local SSL_ERROR_ZERO_RETURN = 6
1✔
178

179
local sslsocket = {
1✔
180
}
181
sslsocket.__index = sslsocket
1✔
182

183
local WAIT_FOR_READ =1
1✔
184
local WAIT_FOR_WRITE =2
1✔
185

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

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

192
    local mode = WAIT_FOR_WRITE
6✔
193

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

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

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

232
function sslsocket.close(self)
1✔
233
    return self.sock:close()
×
234
end
235

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

240
    return self.sock:error() or error_string
×
241
end
242

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

247
function sslsocket.fd(self)
1✔
248
    return self.sock:fd()
×
249
end
250

251
function sslsocket.nonblock(self, nb)
1✔
252
    return self.sock:nonblock(nb)
×
253
end
254

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

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

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

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

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

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

306
    local start = clock.time()
9✔
307

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

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

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

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

347
    -- Not reached.
348
end
349

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

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

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

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

404
function sslsocket.readable(self, timeout)
1✔
405
    return self.sock:readable(timeout)
×
406
end
407

408
local function wrap_accepted_socket(sock, sslctx)
409
    sslctx = sslctx or default_ctx
9✔
410

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

419
    sock:nonblock(true)
9✔
420

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

428
    ffi.C.ERR_clear_error()
9✔
429
    ffi.C.SSL_set_accept_state(ssl);
9✔
430

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

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

441
    local handler_function = handler.handler
9✔
442

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

452
    handler.handler = wrapper
9✔
453

454
    return socket.tcp_server(host, port, handler, timeout)
9✔
455
end
456

457
return {
1✔
458
    tls_server_method = tls_server_method,
1✔
459

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