local base = require "resty.core.base" base.allows_subsystem("http") local debug = require "debug" local ffi = require "ffi" local error = error local assert = assert local tonumber = tonumber local tostring = tostring local type = type local select = select local registry = debug.getregistry() local C = ffi.C local ffi_new = ffi.new local ffi_str = ffi.string local ffi_gc = ffi.gc local get_string_buf = base.get_string_buf local get_size_ptr = base.get_size_ptr local get_request = base.get_request local co_yield = coroutine._yield local option_index = { ["keepalive"] = 1, ["reuseaddr"] = 2, ["tcp-nodelay"] = 3, ["sndbuf"] = 4, ["rcvbuf"] = 5, } ffi.cdef[[ typedef struct ngx_http_lua_socket_tcp_upstream_s ngx_http_lua_socket_tcp_upstream_t; int ngx_http_lua_ffi_socket_tcp_getoption(ngx_http_lua_socket_tcp_upstream_t *u, int opt, int *val, unsigned char *err, size_t *errlen); int ngx_http_lua_ffi_socket_tcp_setoption(ngx_http_lua_socket_tcp_upstream_t *u, int opt, int val, unsigned char *err, size_t *errlen); int ngx_http_lua_ffi_socket_tcp_sslhandshake(ngx_http_request_t *r, ngx_http_lua_socket_tcp_upstream_t *u, void *sess, int enable_session_reuse, ngx_str_t *server_name, int verify, int ocsp_status_req, void *chain, void *pkey, char **errmsg); int ngx_http_lua_ffi_socket_tcp_get_sslhandshake_result(ngx_http_request_t *r, ngx_http_lua_socket_tcp_upstream_t *u, void **sess, char **errmsg, int *openssl_error_code); void ngx_http_lua_ffi_ssl_free_session(void *sess); ]] local output_value_buf = ffi_new("int[1]") local ERR_BUF_SIZE = 4096 local FFI_OK = base.FFI_OK local FFI_ERROR = base.FFI_ERROR local FFI_DONE = base.FFI_DONE local FFI_AGAIN = base.FFI_AGAIN local FFI_NO_REQ_CTX = base.FFI_NO_REQ_CTX local SOCKET_CTX_INDEX = 1 local SOCKET_CLIENT_CERT_INDEX = 6 local SOCKET_CLIENT_PKEY_INDEX = 7 local function get_tcp_socket(cosocket) local tcp_socket = cosocket[SOCKET_CTX_INDEX] if not tcp_socket then error("socket is never created nor connected") end return tcp_socket end local function getoption(cosocket, option) local tcp_socket = get_tcp_socket(cosocket) if option == nil then return nil, 'missing the "option" argument' end if option_index[option] == nil then return nil, "unsupported option " .. tostring(option) end local err = get_string_buf(ERR_BUF_SIZE) local errlen = get_size_ptr() errlen[0] = ERR_BUF_SIZE local rc = C.ngx_http_lua_ffi_socket_tcp_getoption(tcp_socket, option_index[option], output_value_buf, err, errlen) if rc ~= FFI_OK then return nil, ffi_str(err, errlen[0]) end return tonumber(output_value_buf[0]) end local function setoption(cosocket, option, value) local tcp_socket = get_tcp_socket(cosocket) if option == nil then return nil, 'missing the "option" argument' end if value == nil then return nil, 'missing the "value" argument' end if option_index[option] == nil then return nil, "unsupported option " .. tostring(option) end local err = get_string_buf(ERR_BUF_SIZE) local errlen = get_size_ptr() errlen[0] = ERR_BUF_SIZE local rc = C.ngx_http_lua_ffi_socket_tcp_setoption(tcp_socket, option_index[option], value, err, errlen) if rc ~= FFI_OK then return nil, ffi_str(err, errlen[0]) end return true end local errmsg = base.get_errmsg_ptr() local session_ptr = ffi_new("void *[1]") local server_name_str = ffi_new("ngx_str_t[1]") local openssl_error_code = ffi_new("int[1]") local function setclientcert(cosocket, cert, pkey) if not cert and not pkey then cosocket[SOCKET_CLIENT_CERT_INDEX] = nil cosocket[SOCKET_CLIENT_PKEY_INDEX] = nil return true end if not cert or not pkey then return nil, "client certificate must be supplied with corresponding " .. "private key" end if type(cert) ~= "cdata" then return nil, "bad cert arg: cdata expected, got " .. type(cert) end if type(pkey) ~= "cdata" then return nil, "bad pkey arg: cdata expected, got " .. type(pkey) end cosocket[SOCKET_CLIENT_CERT_INDEX] = cert cosocket[SOCKET_CLIENT_PKEY_INDEX] = pkey return true end local function sslhandshake(cosocket, reused_session, server_name, ssl_verify, send_status_req, ...) local n = select("#", ...) if not cosocket or n > 0 then error("ngx.socket sslhandshake: expecting 1 ~ 5 arguments " .. "(including the object), but seen " .. (cosocket and 5 + n or 0)) end local r = get_request() if not r then error("no request found", 2) end session_ptr[0] = type(reused_session) == "cdata" and reused_session or nil if server_name then server_name_str[0].data = server_name server_name_str[0].len = #server_name else server_name_str[0].data = nil server_name_str[0].len = 0 end local u = get_tcp_socket(cosocket) local rc = C.ngx_http_lua_ffi_socket_tcp_sslhandshake(r, u, session_ptr[0], reused_session ~= false, server_name_str, ssl_verify and 1 or 0, send_status_req and 1 or 0, cosocket[SOCKET_CLIENT_CERT_INDEX], cosocket[SOCKET_CLIENT_PKEY_INDEX], errmsg) if rc == FFI_NO_REQ_CTX then error("no request ctx found", 2) end while true do if rc == FFI_ERROR then if openssl_error_code[0] ~= 0 then return nil, openssl_error_code[0] .. ": " .. ffi_str(errmsg[0]) end return nil, ffi_str(errmsg[0]) end if rc == FFI_DONE then return reused_session end if rc == FFI_OK then if reused_session == false then return true end rc = C.ngx_http_lua_ffi_socket_tcp_get_sslhandshake_result(r, u, session_ptr, errmsg, openssl_error_code) assert(rc == FFI_OK) if session_ptr[0] == nil then return session_ptr[0] end return ffi_gc(session_ptr[0], C.ngx_http_lua_ffi_ssl_free_session) end assert(rc == FFI_AGAIN) co_yield() rc = C.ngx_http_lua_ffi_socket_tcp_get_sslhandshake_result(r, u, session_ptr, errmsg, openssl_error_code) end end do local method_table = registry.__tcp_cosocket_mt method_table.getoption = getoption method_table.setoption = setoption method_table.setclientcert = setclientcert method_table.sslhandshake = sslhandshake end return { version = base.version }