1
2/*
3 * Copyright (C) Nginx, Inc.
4 */
5
6
7#include <ngx_config.h>
8#include <ngx_core.h>
9#include <ngx_stream.h>
10
11
12typedef struct {
13    ngx_flag_t      enabled;
14} ngx_stream_ssl_preread_srv_conf_t;
15
16
17typedef struct {
18    size_t          left;
19    size_t          size;
20    u_char         *pos;
21    u_char         *dst;
22    u_char          buf[4];
23    ngx_str_t       host;
24    ngx_log_t      *log;
25    ngx_pool_t     *pool;
26    ngx_uint_t      state;
27} ngx_stream_ssl_preread_ctx_t;
28
29
30static ngx_int_t ngx_stream_ssl_preread_handler(ngx_stream_session_t *s);
31static ngx_int_t ngx_stream_ssl_preread_parse_record(
32    ngx_stream_ssl_preread_ctx_t *ctx, u_char *pos, u_char *last);
33static ngx_int_t ngx_stream_ssl_preread_server_name_variable(
34    ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data);
35static ngx_int_t ngx_stream_ssl_preread_add_variables(ngx_conf_t *cf);
36static void *ngx_stream_ssl_preread_create_srv_conf(ngx_conf_t *cf);
37static char *ngx_stream_ssl_preread_merge_srv_conf(ngx_conf_t *cf, void *parent,
38    void *child);
39static ngx_int_t ngx_stream_ssl_preread_init(ngx_conf_t *cf);
40
41
42static ngx_command_t  ngx_stream_ssl_preread_commands[] = {
43
44    { ngx_string("ssl_preread"),
45      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG,
46      ngx_conf_set_flag_slot,
47      NGX_STREAM_SRV_CONF_OFFSET,
48      offsetof(ngx_stream_ssl_preread_srv_conf_t, enabled),
49      NULL },
50
51      ngx_null_command
52};
53
54
55static ngx_stream_module_t  ngx_stream_ssl_preread_module_ctx = {
56    ngx_stream_ssl_preread_add_variables,   /* preconfiguration */
57    ngx_stream_ssl_preread_init,            /* postconfiguration */
58
59    NULL,                                   /* create main configuration */
60    NULL,                                   /* init main configuration */
61
62    ngx_stream_ssl_preread_create_srv_conf, /* create server configuration */
63    ngx_stream_ssl_preread_merge_srv_conf   /* merge server configuration */
64};
65
66
67ngx_module_t  ngx_stream_ssl_preread_module = {
68    NGX_MODULE_V1,
69    &ngx_stream_ssl_preread_module_ctx,     /* module context */
70    ngx_stream_ssl_preread_commands,        /* module directives */
71    NGX_STREAM_MODULE,                      /* module type */
72    NULL,                                   /* init master */
73    NULL,                                   /* init module */
74    NULL,                                   /* init process */
75    NULL,                                   /* init thread */
76    NULL,                                   /* exit thread */
77    NULL,                                   /* exit process */
78    NULL,                                   /* exit master */
79    NGX_MODULE_V1_PADDING
80};
81
82
83static ngx_stream_variable_t  ngx_stream_ssl_preread_vars[] = {
84
85    { ngx_string("ssl_preread_server_name"), NULL,
86      ngx_stream_ssl_preread_server_name_variable, 0, 0, 0 },
87
88    { ngx_null_string, NULL, NULL, 0, 0, 0 }
89};
90
91
92static ngx_int_t
93ngx_stream_ssl_preread_handler(ngx_stream_session_t *s)
94{
95    u_char                             *last, *p;
96    size_t                              len;
97    ngx_int_t                           rc;
98    ngx_connection_t                   *c;
99    ngx_stream_ssl_preread_ctx_t       *ctx;
100    ngx_stream_ssl_preread_srv_conf_t  *sscf;
101
102    c = s->connection;
103
104    ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, "ssl preread handler");
105
106    sscf = ngx_stream_get_module_srv_conf(s, ngx_stream_ssl_preread_module);
107
108    if (!sscf->enabled) {
109        return NGX_DECLINED;
110    }
111
112    if (c->type != SOCK_STREAM) {
113        return NGX_DECLINED;
114    }
115
116    if (c->buffer == NULL) {
117        return NGX_AGAIN;
118    }
119
120    ctx = ngx_stream_get_module_ctx(s, ngx_stream_ssl_preread_module);
121    if (ctx == NULL) {
122        ctx = ngx_pcalloc(c->pool, sizeof(ngx_stream_ssl_preread_ctx_t));
123        if (ctx == NULL) {
124            return NGX_ERROR;
125        }
126
127        ngx_stream_set_ctx(s, ctx, ngx_stream_ssl_preread_module);
128
129        ctx->pool = c->pool;
130        ctx->log = c->log;
131        ctx->pos = c->buffer->pos;
132    }
133
134    p = ctx->pos;
135    last = c->buffer->last;
136
137    while (last - p >= 5) {
138
139        if (p[0] != 0x16) {
140            ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0,
141                           "ssl preread: not a handshake");
142            return NGX_DECLINED;
143        }
144
145        if (p[1] != 3) {
146            ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0,
147                           "ssl preread: unsupported SSL version");
148            return NGX_DECLINED;
149        }
150
151        len = (p[3] << 8) + p[4];
152
153        /* read the whole record before parsing */
154        if ((size_t) (last - p) < len + 5) {
155            break;
156        }
157
158        p += 5;
159
160        rc = ngx_stream_ssl_preread_parse_record(ctx, p, p + len);
161        if (rc != NGX_AGAIN) {
162            return rc;
163        }
164
165        p += len;
166    }
167
168    ctx->pos = p;
169
170    return NGX_AGAIN;
171}
172
173
174static ngx_int_t
175ngx_stream_ssl_preread_parse_record(ngx_stream_ssl_preread_ctx_t *ctx,
176    u_char *pos, u_char *last)
177{
178    size_t   left, n, size;
179    u_char  *dst, *p;
180
181    enum {
182        sw_start = 0,
183        sw_header,          /* handshake msg_type, length */
184        sw_head_tail,       /* version, random */
185        sw_sid_len,         /* session_id length */
186        sw_sid,             /* session_id */
187        sw_cs_len,          /* cipher_suites length */
188        sw_cs,              /* cipher_suites */
189        sw_cm_len,          /* compression_methods length */
190        sw_cm,              /* compression_methods */
191        sw_ext,             /* extension */
192        sw_ext_header,      /* extension_type, extension_data length */
193        sw_sni_len,         /* SNI length */
194        sw_sni_host_head,   /* SNI name_type, host_name length */
195        sw_sni_host         /* SNI host_name */
196    } state;
197
198    ngx_log_debug2(NGX_LOG_DEBUG_STREAM, ctx->log, 0,
199                   "ssl preread: state %ui left %z", ctx->state, ctx->left);
200
201    state = ctx->state;
202    size = ctx->size;
203    left = ctx->left;
204    dst = ctx->dst;
205    p = ctx->buf;
206
207    for ( ;; ) {
208        n = ngx_min((size_t) (last - pos), size);
209
210        if (dst) {
211            dst = ngx_cpymem(dst, pos, n);
212        }
213
214        pos += n;
215        size -= n;
216        left -= n;
217
218        if (size != 0) {
219            break;
220        }
221
222        switch (state) {
223
224        case sw_start:
225            state = sw_header;
226            dst = p;
227            size = 4;
228            left = size;
229            break;
230
231        case sw_header:
232            if (p[0] != 1) {
233                ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0,
234                               "ssl preread: not a client hello");
235                return NGX_DECLINED;
236            }
237
238            state = sw_head_tail;
239            dst = NULL;
240            size = 34;
241            left = (p[1] << 16) + (p[2] << 8) + p[3];
242            break;
243
244        case sw_head_tail:
245            state = sw_sid_len;
246            dst = p;
247            size = 1;
248            break;
249
250        case sw_sid_len:
251            state = sw_sid;
252            dst = NULL;
253            size = p[0];
254            break;
255
256        case sw_sid:
257            state = sw_cs_len;
258            dst = p;
259            size = 2;
260            break;
261
262        case sw_cs_len:
263            state = sw_cs;
264            dst = NULL;
265            size = (p[0] << 8) + p[1];
266            break;
267
268        case sw_cs:
269            state = sw_cm_len;
270            dst = p;
271            size = 1;
272            break;
273
274        case sw_cm_len:
275            state = sw_cm;
276            dst = NULL;
277            size = p[0];
278            break;
279
280        case sw_cm:
281            if (left == 0) {
282                /* no extensions */
283                return NGX_OK;
284            }
285
286            state = sw_ext;
287            dst = p;
288            size = 2;
289            break;
290
291        case sw_ext:
292            if (left == 0) {
293                return NGX_OK;
294            }
295
296            state = sw_ext_header;
297            dst = p;
298            size = 4;
299            break;
300
301        case sw_ext_header:
302            if (p[0] == 0 && p[1] == 0) {
303                /* SNI extension */
304                state = sw_sni_len;
305                dst = NULL;
306                size = 2;
307                break;
308            }
309
310            state = sw_ext;
311            dst = NULL;
312            size = (p[2] << 8) + p[3];
313            break;
314
315        case sw_sni_len:
316            state = sw_sni_host_head;
317            dst = p;
318            size = 3;
319            break;
320
321        case sw_sni_host_head:
322            if (p[0] != 0) {
323                ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0,
324                               "ssl preread: SNI hostname type is not DNS");
325                return NGX_DECLINED;
326            }
327
328            state = sw_sni_host;
329            size = (p[1] << 8) + p[2];
330
331            ctx->host.data = ngx_pnalloc(ctx->pool, size);
332            if (ctx->host.data == NULL) {
333                return NGX_ERROR;
334            }
335
336            dst = ctx->host.data;
337            break;
338
339        case sw_sni_host:
340            ctx->host.len = (p[1] << 8) + p[2];
341
342            ngx_log_debug1(NGX_LOG_DEBUG_STREAM, ctx->log, 0,
343                           "ssl preread: SNI hostname \"%V\"", &ctx->host);
344            return NGX_OK;
345        }
346
347        if (left < size) {
348           ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0,
349                          "ssl preread: failed to parse handshake");
350           return NGX_DECLINED;
351        }
352    }
353
354    ctx->state = state;
355    ctx->size = size;
356    ctx->left = left;
357    ctx->dst = dst;
358
359    return NGX_AGAIN;
360}
361
362
363static ngx_int_t
364ngx_stream_ssl_preread_server_name_variable(ngx_stream_session_t *s,
365    ngx_variable_value_t *v, uintptr_t data)
366{
367    ngx_stream_ssl_preread_ctx_t  *ctx;
368
369    ctx = ngx_stream_get_module_ctx(s, ngx_stream_ssl_preread_module);
370
371    if (ctx == NULL) {
372        v->not_found = 1;
373        return NGX_OK;
374    }
375
376    v->valid = 1;
377    v->no_cacheable = 0;
378    v->not_found = 0;
379    v->len = ctx->host.len;
380    v->data = ctx->host.data;
381
382    return NGX_OK;
383}
384
385
386static ngx_int_t
387ngx_stream_ssl_preread_add_variables(ngx_conf_t *cf)
388{
389    ngx_stream_variable_t  *var, *v;
390
391    for (v = ngx_stream_ssl_preread_vars; v->name.len; v++) {
392        var = ngx_stream_add_variable(cf, &v->name, v->flags);
393        if (var == NULL) {
394            return NGX_ERROR;
395        }
396
397        var->get_handler = v->get_handler;
398        var->data = v->data;
399    }
400
401    return NGX_OK;
402}
403
404
405static void *
406ngx_stream_ssl_preread_create_srv_conf(ngx_conf_t *cf)
407{
408    ngx_stream_ssl_preread_srv_conf_t  *conf;
409
410    conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_ssl_preread_srv_conf_t));
411    if (conf == NULL) {
412        return NULL;
413    }
414
415    conf->enabled = NGX_CONF_UNSET;
416
417    return conf;
418}
419
420
421static char *
422ngx_stream_ssl_preread_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
423{
424    ngx_stream_ssl_preread_srv_conf_t *prev = parent;
425    ngx_stream_ssl_preread_srv_conf_t *conf = child;
426
427    ngx_conf_merge_value(conf->enabled, prev->enabled, 0);
428
429    return NGX_CONF_OK;
430}
431
432
433static ngx_int_t
434ngx_stream_ssl_preread_init(ngx_conf_t *cf)
435{
436    ngx_stream_handler_pt        *h;
437    ngx_stream_core_main_conf_t  *cmcf;
438
439    cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module);
440
441    h = ngx_array_push(&cmcf->phases[NGX_STREAM_PREREAD_PHASE].handlers);
442    if (h == NULL) {
443        return NGX_ERROR;
444    }
445
446    *h = ngx_stream_ssl_preread_handler;
447
448    return NGX_OK;
449}
450