Skip to content

Commit 3ee8cef

Browse files
author
Rob Mueller
committed
feature: implement serversslhandshake method on downstream sockets
tcpsock:sslhandshake does a client side ssl handshake for upstream sockets. normally you just specify `ssl` on the listen directive for downstream sockets. however there are certain cases where you want to be able to take a plaintext downstream connection and upgrade it to an ssl encrypted one, such as legacy SMTP STARTTLS this implements a new sock:serversslhandshake method. it uses ssl certificate setup via the existing ssl_certificate and ssl_certificate_key configuration options. it only adds this method to downstream socket connections.
1 parent db8022d commit 3ee8cef

File tree

4 files changed

+887
-0
lines changed

4 files changed

+887
-0
lines changed

src/ngx_stream_lua_socket_tcp.c

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ static int ngx_stream_lua_socket_tcp_bind(lua_State *L);
3232
static int ngx_stream_lua_socket_tcp_connect(lua_State *L);
3333
#if (NGX_STREAM_SSL)
3434
static int ngx_stream_lua_socket_tcp_sslhandshake(lua_State *L);
35+
static int ngx_stream_lua_socket_tcp_serversslhandshake(lua_State *L);
3536
#endif
3637
static int ngx_stream_lua_socket_tcp_receive(lua_State *L);
3738
static int ngx_stream_lua_socket_tcp_receiveany(lua_State *L);
@@ -181,6 +182,12 @@ static int ngx_stream_lua_ssl_handshake_retval_handler(
181182
ngx_stream_lua_request_t *r, ngx_stream_lua_socket_tcp_upstream_t *u,
182183
lua_State *L);
183184
static void ngx_stream_lua_ssl_handshake_handler(ngx_connection_t *c);
185+
static int ngx_stream_lua_server_ssl_handshake_retval_handler(
186+
ngx_stream_lua_request_t *r, ngx_stream_lua_socket_tcp_upstream_t *u,
187+
lua_State *L);
188+
static void ngx_stream_lua_ssl_handshake_session_info(ngx_connection_t *c,
189+
lua_State *L);
190+
static void ngx_stream_lua_server_ssl_handshake_handler(ngx_connection_t *c);
184191
static int ngx_stream_lua_ssl_free_session(lua_State *L);
185192
#endif
186193
static void ngx_stream_lua_socket_tcp_close_connection(ngx_connection_t *c);
@@ -327,6 +334,13 @@ ngx_stream_lua_inject_socket_tcp_api(ngx_log_t *log, lua_State *L)
327334
lua_pushcfunction(L, ngx_stream_lua_socket_tcp_shutdown);
328335
lua_setfield(L, -2, "shutdown");
329336

337+
#if (NGX_STREAM_SSL)
338+
339+
lua_pushcfunction(L, ngx_stream_lua_socket_tcp_serversslhandshake);
340+
lua_setfield(L, -2, "serversslhandshake");
341+
342+
#endif
343+
330344
lua_pushvalue(L, -1);
331345
lua_setfield(L, -2, "__index");
332346

@@ -2036,6 +2050,259 @@ ngx_stream_lua_ssl_handshake_retval_handler(ngx_stream_lua_request_t *r,
20362050
return 1;
20372051
}
20382052

2053+
2054+
static int
2055+
ngx_stream_lua_socket_tcp_serversslhandshake(lua_State *L)
2056+
{
2057+
int n, top;
2058+
ngx_int_t rc;
2059+
ngx_connection_t *c;
2060+
2061+
ngx_stream_lua_request_t *r;
2062+
ngx_stream_lua_ctx_t *ctx;
2063+
ngx_stream_lua_co_ctx_t *coctx;
2064+
ngx_stream_lua_socket_tcp_upstream_t *u;
2065+
ngx_stream_ssl_srv_conf_t *sscf;
2066+
2067+
/* Lua function arguments: self */
2068+
2069+
n = lua_gettop(L);
2070+
if (n != 1) {
2071+
return luaL_error(L, "ngx.socket serversslhandshake: expecting 1 "
2072+
"argument (the object), but seen %d", n);
2073+
}
2074+
2075+
r = ngx_stream_lua_get_req(L);
2076+
if (r == NULL) {
2077+
return luaL_error(L, "no request found");
2078+
}
2079+
2080+
ngx_log_debug0(NGX_LOG_DEBUG_STREAM, r->connection->log, 0,
2081+
"stream lua tcp socket server ssl handshake");
2082+
2083+
luaL_checktype(L, 1, LUA_TTABLE);
2084+
2085+
lua_rawgeti(L, 1, SOCKET_CTX_INDEX);
2086+
u = lua_touserdata(L, -1);
2087+
2088+
if (u == NULL
2089+
|| u->peer.connection == NULL
2090+
|| u->read_closed
2091+
|| u->write_closed)
2092+
{
2093+
lua_pushnil(L);
2094+
lua_pushliteral(L, "closed");
2095+
return 2;
2096+
}
2097+
2098+
if (u->request != r) {
2099+
return luaL_error(L, "bad request");
2100+
}
2101+
2102+
ngx_stream_lua_socket_check_busy_connecting(r, u, L);
2103+
ngx_stream_lua_socket_check_busy_reading(r, u, L);
2104+
ngx_stream_lua_socket_check_busy_writing(r, u, L);
2105+
2106+
if (!u->raw_downstream && !u->body_downstream) {
2107+
lua_pushnil(L);
2108+
lua_pushliteral(L, "only supported for downstream socket");
2109+
return 2;
2110+
}
2111+
2112+
/* For downstream sockets, the connection is r->connection */
2113+
c = r->connection;
2114+
2115+
if (c->ssl && c->ssl->handshaked) {
2116+
/* SSL handshake already completed, return session info */
2117+
ngx_stream_lua_ssl_handshake_session_info(c, L);
2118+
return 1;
2119+
}
2120+
2121+
sscf = ngx_stream_get_module_srv_conf(r->session, ngx_stream_ssl_module);
2122+
2123+
if (sscf == NULL || sscf->ssl.ctx == NULL) {
2124+
lua_pushnil(L);
2125+
lua_pushliteral(L, "ssl not configured for this server");
2126+
return 2;
2127+
}
2128+
2129+
if (ngx_ssl_create_connection(&sscf->ssl, c, NGX_SSL_BUFFER) != NGX_OK) {
2130+
lua_pushnil(L);
2131+
lua_pushliteral(L, "failed to create ssl connection");
2132+
return 2;
2133+
}
2134+
2135+
ctx = ngx_stream_lua_get_module_ctx(r, ngx_stream_lua_module);
2136+
if (ctx == NULL) {
2137+
return luaL_error(L, "no ctx found");
2138+
}
2139+
2140+
coctx = ctx->cur_co_ctx;
2141+
2142+
c->sendfile = 0;
2143+
2144+
u->write_co_ctx = coctx;
2145+
2146+
rc = ngx_ssl_handshake(c);
2147+
2148+
dd("ngx_ssl_handshake returned %d", (int) rc);
2149+
2150+
if (rc == NGX_AGAIN) {
2151+
if (c->write->timer_set) {
2152+
ngx_del_timer(c->write);
2153+
}
2154+
2155+
ngx_add_timer(c->read, u->read_timeout);
2156+
2157+
u->conn_waiting = 1;
2158+
u->write_prepare_retvals = ngx_stream_lua_server_ssl_handshake_retval_handler;
2159+
2160+
ngx_stream_lua_cleanup_pending_operation(coctx);
2161+
coctx->cleanup = ngx_stream_lua_coctx_cleanup;
2162+
coctx->data = u;
2163+
2164+
c->ssl->handler = ngx_stream_lua_server_ssl_handshake_handler;
2165+
2166+
if (ctx->entered_content_phase) {
2167+
r->write_event_handler = ngx_stream_lua_content_wev_handler;
2168+
2169+
} else {
2170+
r->write_event_handler = ngx_stream_lua_core_run_phases;
2171+
}
2172+
2173+
return lua_yield(L, 0);
2174+
}
2175+
2176+
top = lua_gettop(L);
2177+
ngx_stream_lua_server_ssl_handshake_handler(c);
2178+
return lua_gettop(L) - top;
2179+
}
2180+
2181+
2182+
static void
2183+
ngx_stream_lua_server_ssl_handshake_handler(ngx_connection_t *c)
2184+
{
2185+
int waiting;
2186+
lua_State *L;
2187+
ngx_stream_lua_request_t *r;
2188+
ngx_stream_session_t *s;
2189+
2190+
ngx_stream_lua_ctx_t *ctx;
2191+
ngx_stream_lua_socket_tcp_upstream_t *u;
2192+
2193+
/* For downstream sockets, c->data points to the session. */
2194+
s = c->data;
2195+
2196+
/* Get the context from the session */
2197+
ctx = ngx_stream_get_module_ctx(s, ngx_stream_lua_module);
2198+
if (ctx == NULL) {
2199+
return;
2200+
}
2201+
2202+
r = ctx->request;
2203+
/* For downstream sockets, u is stored in ctx->downstream */
2204+
u = ctx->downstream;
2205+
2206+
c->read->handler = ngx_stream_lua_request_handler;
2207+
c->write->handler = ngx_stream_lua_request_handler;
2208+
2209+
waiting = u->conn_waiting;
2210+
2211+
L = u->write_co_ctx->co;
2212+
2213+
if (c->read->timedout) {
2214+
lua_pushnil(L);
2215+
lua_pushliteral(L, "timeout");
2216+
goto failed;
2217+
}
2218+
2219+
if (c->read->timer_set) {
2220+
ngx_del_timer(c->read);
2221+
}
2222+
2223+
r = u->request;
2224+
if (r == NULL) {
2225+
return;
2226+
}
2227+
2228+
if (c->ssl->handshaked) {
2229+
if (waiting) {
2230+
ngx_stream_lua_socket_handle_conn_success(r, u);
2231+
2232+
} else {
2233+
(void) ngx_stream_lua_server_ssl_handshake_retval_handler(r, u, L);
2234+
}
2235+
2236+
return;
2237+
}
2238+
2239+
lua_pushnil(L);
2240+
lua_pushliteral(L, "handshake failed");
2241+
2242+
failed:
2243+
2244+
if (waiting) {
2245+
ngx_stream_lua_socket_handle_conn_error(r, u,
2246+
NGX_STREAM_LUA_SOCKET_FT_SSL);
2247+
2248+
} else {
2249+
(void) ngx_stream_lua_socket_write_error_retval_handler(r, u, L);
2250+
}
2251+
}
2252+
2253+
2254+
static void
2255+
ngx_stream_lua_ssl_handshake_session_info(ngx_connection_t *c, lua_State *L)
2256+
{
2257+
const char *protocol;
2258+
SSL_CIPHER *cipher;
2259+
const char *cipher_name;
2260+
2261+
/* Create a table with SSL session information */
2262+
lua_createtable(L, 0, 3);
2263+
2264+
/* Add protocol version */
2265+
protocol = SSL_get_version(c->ssl->connection);
2266+
if (protocol) {
2267+
lua_pushstring(L, protocol);
2268+
lua_setfield(L, -2, "protocol");
2269+
}
2270+
2271+
/* Add cipher name */
2272+
cipher = (SSL_CIPHER *) SSL_get_current_cipher(c->ssl->connection);
2273+
if (cipher) {
2274+
cipher_name = SSL_CIPHER_get_name(cipher);
2275+
if (cipher_name) {
2276+
lua_pushstring(L, cipher_name);
2277+
lua_setfield(L, -2, "cipher");
2278+
}
2279+
}
2280+
2281+
/* Add session reused flag */
2282+
lua_pushboolean(L, SSL_session_reused(c->ssl->connection));
2283+
lua_setfield(L, -2, "session_reused");
2284+
}
2285+
2286+
2287+
static int
2288+
ngx_stream_lua_server_ssl_handshake_retval_handler(ngx_stream_lua_request_t *r,
2289+
ngx_stream_lua_socket_tcp_upstream_t *u, lua_State *L)
2290+
{
2291+
ngx_connection_t *c;
2292+
2293+
/* Check if an error occurred during the handshake */
2294+
if (u->ft_type) {
2295+
return ngx_stream_lua_socket_conn_error_retval_handler(r, u, L);
2296+
}
2297+
2298+
/* For downstream sockets, the connection is r->connection */
2299+
c = r->connection;
2300+
2301+
ngx_stream_lua_ssl_handshake_session_info(c, L);
2302+
2303+
return 1;
2304+
}
2305+
20392306
#endif /* NGX_STREAM_SSL */
20402307

20412308

t/165-serversslhandshake.t

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# vim:set ft= ts=4 sw=4 et fdm=marker:
2+
3+
use Test::Nginx::Socket::Lua::Stream;
4+
5+
repeat_each(2);
6+
7+
plan tests => repeat_each() * 7;
8+
9+
#log_level 'warn';
10+
log_level 'debug';
11+
12+
no_long_string();
13+
#no_diff();
14+
15+
run_tests();
16+
17+
__DATA__
18+
19+
=== TEST 1: serversslhandshake without SSL configured should fail
20+
--- stream_server_config
21+
content_by_lua_block {
22+
local sock, err = ngx.req.socket(true)
23+
if not sock then
24+
ngx.say("failed to get socket: ", err)
25+
return
26+
end
27+
28+
-- Consume the initial test line
29+
local line, err = sock:receive()
30+
if not line then
31+
ngx.say("failed to receive: ", err)
32+
return
33+
end
34+
35+
ngx.say("method exists: ", type(sock.serversslhandshake) == "function")
36+
37+
local session, err = sock:serversslhandshake()
38+
ngx.say("error: ", err or "unexpected success")
39+
}
40+
41+
--- stream_request
42+
test
43+
--- stream_response
44+
method exists: true
45+
error: ssl not configured for this server
46+
--- no_error_log
47+
[alert]
48+
49+
50+
51+
=== TEST 2: serversslhandshake method doesn't exist on non-downstream socket
52+
--- stream_server_config
53+
content_by_lua_block {
54+
local sock = ngx.socket.tcp()
55+
ngx.say("method exists: ", type(sock.serversslhandshake) == "function")
56+
}
57+
58+
--- stream_response
59+
method exists: false
60+
--- no_error_log
61+
[error]
62+
[alert]
63+
64+

0 commit comments

Comments
 (0)