Skip to content

Commit fa988ed

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 fa988ed

File tree

4 files changed

+888
-0
lines changed

4 files changed

+888
-0
lines changed

src/ngx_stream_lua_socket_tcp.c

Lines changed: 268 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,260 @@ 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_int_t rc;
2188+
ngx_stream_lua_request_t *r;
2189+
ngx_stream_session_t *s;
2190+
2191+
ngx_stream_lua_ctx_t *ctx;
2192+
ngx_stream_lua_socket_tcp_upstream_t *u;
2193+
2194+
/* For downstream sockets, c->data points to the session. */
2195+
s = c->data;
2196+
2197+
/* Get the context from the session */
2198+
ctx = ngx_stream_get_module_ctx(s, ngx_stream_lua_module);
2199+
if (ctx == NULL) {
2200+
return;
2201+
}
2202+
2203+
r = ctx->request;
2204+
/* For downstream sockets, u is stored in ctx->downstream */
2205+
u = ctx->downstream;
2206+
2207+
c->read->handler = ngx_stream_lua_request_handler;
2208+
c->write->handler = ngx_stream_lua_request_handler;
2209+
2210+
waiting = u->conn_waiting;
2211+
2212+
L = u->write_co_ctx->co;
2213+
2214+
if (c->read->timedout) {
2215+
lua_pushnil(L);
2216+
lua_pushliteral(L, "timeout");
2217+
goto failed;
2218+
}
2219+
2220+
if (c->read->timer_set) {
2221+
ngx_del_timer(c->read);
2222+
}
2223+
2224+
r = u->request;
2225+
if (r == NULL) {
2226+
return;
2227+
}
2228+
2229+
if (c->ssl->handshaked) {
2230+
if (waiting) {
2231+
ngx_stream_lua_socket_handle_conn_success(r, u);
2232+
2233+
} else {
2234+
(void) ngx_stream_lua_server_ssl_handshake_retval_handler(r, u, L);
2235+
}
2236+
2237+
return;
2238+
}
2239+
2240+
lua_pushnil(L);
2241+
lua_pushliteral(L, "handshake failed");
2242+
2243+
failed:
2244+
2245+
if (waiting) {
2246+
ngx_stream_lua_socket_handle_conn_error(r, u,
2247+
NGX_STREAM_LUA_SOCKET_FT_SSL);
2248+
2249+
} else {
2250+
(void) ngx_stream_lua_socket_write_error_retval_handler(r, u, L);
2251+
}
2252+
}
2253+
2254+
2255+
static void
2256+
ngx_stream_lua_ssl_handshake_session_info(ngx_connection_t *c, lua_State *L)
2257+
{
2258+
const char *protocol;
2259+
SSL_CIPHER *cipher;
2260+
const char *cipher_name;
2261+
2262+
/* Create a table with SSL session information */
2263+
lua_createtable(L, 0, 3);
2264+
2265+
/* Add protocol version */
2266+
protocol = SSL_get_version(c->ssl->connection);
2267+
if (protocol) {
2268+
lua_pushstring(L, protocol);
2269+
lua_setfield(L, -2, "protocol");
2270+
}
2271+
2272+
/* Add cipher name */
2273+
cipher = (SSL_CIPHER *) SSL_get_current_cipher(c->ssl->connection);
2274+
if (cipher) {
2275+
cipher_name = SSL_CIPHER_get_name(cipher);
2276+
if (cipher_name) {
2277+
lua_pushstring(L, cipher_name);
2278+
lua_setfield(L, -2, "cipher");
2279+
}
2280+
}
2281+
2282+
/* Add session reused flag */
2283+
lua_pushboolean(L, SSL_session_reused(c->ssl->connection));
2284+
lua_setfield(L, -2, "session_reused");
2285+
}
2286+
2287+
2288+
static int
2289+
ngx_stream_lua_server_ssl_handshake_retval_handler(ngx_stream_lua_request_t *r,
2290+
ngx_stream_lua_socket_tcp_upstream_t *u, lua_State *L)
2291+
{
2292+
ngx_connection_t *c;
2293+
2294+
/* Check if an error occurred during the handshake */
2295+
if (u->ft_type) {
2296+
return ngx_stream_lua_socket_conn_error_retval_handler(r, u, L);
2297+
}
2298+
2299+
/* For downstream sockets, the connection is r->connection */
2300+
c = r->connection;
2301+
2302+
ngx_stream_lua_ssl_handshake_session_info(c, L);
2303+
2304+
return 1;
2305+
}
2306+
20392307
#endif /* NGX_STREAM_SSL */
20402308

20412309

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)