int h2_allows_h2_upgrade(conn_rec *c) { const h2_config *cfg = h2_config_get(c); int h2_upgrade = h2_config_geti(cfg, H2_CONF_UPGRADE); return h2_upgrade > 0 || (h2_upgrade < 0 && !h2_h2_is_tls(c)); }
static int h2_alt_svc_handler(request_rec *r) { h2_ctx *ctx; h2_config *cfg; int i; if (r->connection->keepalives > 0) { /* Only announce Alt-Svc on the first response */ return DECLINED; } ctx = h2_ctx_rget(r); if (h2_ctx_is_active(ctx) || h2_ctx_is_task(ctx)) { return DECLINED; } cfg = h2_config_rget(r); if (r->hostname && cfg && cfg->alt_svcs && cfg->alt_svcs->nelts > 0) { const char *alt_svc_used = apr_table_get(r->headers_in, "Alt-Svc-Used"); if (!alt_svc_used) { /* We have alt-svcs defined and client is not already using * one, announce the services that were configured and match. * The security of this connection determines if we allow * other host names or ports only. */ const char *alt_svc = ""; const char *svc_ma = ""; int secure = h2_h2_is_tls(r->connection); int ma = h2_config_geti(cfg, H2_CONF_ALT_SVC_MAX_AGE); if (ma >= 0) { svc_ma = apr_psprintf(r->pool, "; ma=%d", ma); } ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "h2_alt_svc: announce %s for %s:%d", (secure? "secure" : "insecure"), r->hostname, (int)r->server->port); for (i = 0; i < cfg->alt_svcs->nelts; ++i) { h2_alt_svc *as = h2_alt_svc_IDX(cfg->alt_svcs, i); const char *ahost = as->host; if (ahost && !apr_strnatcasecmp(ahost, r->hostname)) { ahost = NULL; } if (secure || !ahost) { alt_svc = apr_psprintf(r->pool, "%s%s%s=\"%s:%d\"%s", alt_svc, (*alt_svc? ", " : ""), as->alpn, ahost? ahost : "", as->port, svc_ma); } } if (*alt_svc) { apr_table_set(r->headers_out, "Alt-Svc", alt_svc); } } } return DECLINED; }
int h2_is_acceptable_connection(conn_rec *c, int require_all) { int is_tls = h2_h2_is_tls(c); const h2_config *cfg = h2_config_get(c); if (is_tls && h2_config_geti(cfg, H2_CONF_MODERN_TLS_ONLY) > 0) { /* Check TLS connection for modern TLS parameters, as defined in * RFC 7540 and https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility */ apr_pool_t *pool = c->pool; server_rec *s = c->base_server; char *val; if (!opt_ssl_var_lookup) { /* unable to check */ return 0; } /* Need Tlsv1.2 or higher, rfc 7540, ch. 9.2 */ val = opt_ssl_var_lookup(pool, s, c, NULL, (char*)"SSL_PROTOCOL"); if (val && *val) { if (strncmp("TLS", val, 3) || !strcmp("TLSv1", val) || !strcmp("TLSv1.1", val)) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03050) "h2_h2(%ld): tls protocol not suitable: %s", (long)c->id, val); return 0; } } else if (require_all) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03051) "h2_h2(%ld): tls protocol is indetermined", (long)c->id); return 0; } /* Check TLS cipher blacklist */ val = opt_ssl_var_lookup(pool, s, c, NULL, (char*)"SSL_CIPHER"); if (val && *val) { const char *source; if (cipher_is_blacklisted(val, &source)) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03052) "h2_h2(%ld): tls cipher %s blacklisted by %s", (long)c->id, val, source); return 0; } } else if (require_all) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03053) "h2_h2(%ld): tls cipher is indetermined", (long)c->id); return 0; } } return 1; }
int h2_h2_pre_conn(conn_rec* c, void *arg) { ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "h2_h2, pre_connection, start"); h2_ctx *ctx = h2_ctx_get(c, 0); if (!ctx) { ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "h2_h2, pre_connection, no ctx"); /* We have not seen this one yet, are we active? */ h2_config *cfg = h2_config_get(c); if (!h2_config_geti(cfg, H2_CONF_ENABLED)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "h2_h2, pre_connection, h2 not enabled"); return DECLINED; } /* Are we using TLS on this connection? */ if (!h2_h2_is_tls(c)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "h2_h2, pre_connection, no TLS"); return DECLINED; } /* Does mod_ssl offer ALPN/NPN support? */ if (opt_ssl_register_alpn == NULL && opt_ssl_register_npn == NULL) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "h2_h2, pre_connection, no ALPN/NPN support in mod_ssl"); return DECLINED; } ctx = h2_ctx_get(c, 1); if (opt_ssl_register_alpn) { opt_ssl_register_alpn(c, h2_h2_alpn_propose, h2_h2_alpn_negotiated); } if (opt_ssl_register_npn) { opt_ssl_register_npn(c, h2_h2_npn_advertise, h2_h2_npn_negotiated); } ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "h2_h2, pre_connection, ALPN callback registered"); ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "h2_h2, pre_connection, end"); } else if (h2_ctx_is_task(c)) { /* A connection that represents a http2 stream from another connection. */ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "h2_h2, pre_connection, found stream task"); h2_task *task = h2_ctx_get_task(ctx); return h2_task_pre_conn(task, c); } return DECLINED; }
static int h2_protocol_switch(conn_rec *c, request_rec *r, server_rec *s, const char *protocol) { int found = 0; const char **protos = h2_h2_is_tls(c)? h2_tls_protos : h2_clear_protos; const char **p = protos; (void)s; while (*p) { if (!strcmp(*p, protocol)) { found = 1; break; } p++; } if (found) { h2_ctx *ctx = h2_ctx_get(c, 1); ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "switching protocol to '%s'", protocol); h2_ctx_protocol_set(ctx, protocol); h2_ctx_server_set(ctx, s); if (r != NULL) { apr_status_t status; /* Switching in the middle of a request means that * we have to send out the response to this one in h2 * format. So we need to take over the connection * right away. */ ap_remove_input_filter_byhandle(r->input_filters, "http_in"); ap_remove_input_filter_byhandle(r->input_filters, "reqtimeout"); ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER"); /* Ok, start an h2_conn on this one. */ h2_ctx_server_set(ctx, r->server); status = h2_conn_setup(ctx, r->connection, r); if (status != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(03088) "session setup"); return status; } h2_conn_run(ctx, c); return DONE; } return DONE; } return DECLINED; }
int h2_allows_h2_direct(conn_rec *c) { const h2_config *cfg = h2_config_get(c); int is_tls = h2_h2_is_tls(c); const char *needed_protocol = is_tls? "h2" : "h2c"; int h2_direct = h2_config_geti(cfg, H2_CONF_DIRECT); if (h2_direct < 0) { h2_direct = is_tls? 0 : 1; } return (h2_direct && ap_is_allowed_protocol(c, NULL, NULL, needed_protocol)); }
static int h2_protocol_switch(conn_rec *c, request_rec *r, server_rec *s, const char *protocol) { int found = 0; const char **protos = h2_h2_is_tls(c)? h2_tls_protos : h2_clear_protos; const char **p = protos; (void)s; while (*p) { if (!strcmp(*p, protocol)) { found = 1; break; } p++; } if (found) { h2_ctx *ctx = h2_ctx_get(c); ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "switching protocol to '%s'", protocol); h2_ctx_protocol_set(ctx, protocol); h2_ctx_server_set(ctx, s); if (r != NULL) { apr_status_t status; /* Switching in the middle of a request means that * we have to send out the response to this one in h2 * format. So we need to take over the connection * right away. */ ap_remove_input_filter_byhandle(r->input_filters, "http_in"); ap_remove_input_filter_byhandle(r->input_filters, "reqtimeout"); /* Ok, start an h2_conn on this one. */ status = h2_conn_rprocess(r); if (status != DONE) { /* Nothing really to do about this. */ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, "session proessed, unexpected status"); } } return DONE; } return DECLINED; }
int h2_allows_h2_direct(conn_rec *c) { const h2_config *cfg = h2_config_get(c); int h2_direct = h2_config_geti(cfg, H2_CONF_DIRECT); if (h2_direct < 0) { if (h2_h2_is_tls(c)) { /* disabled by default on TLS */ h2_direct = 0; } else { /* enabled if "Protocols h2c" is configured */ h2_direct = ap_is_allowed_protocol(c, NULL, NULL, "h2c"); } } return !!h2_direct; }
apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c) { h2_config *cfg = h2_config_get(c); io->connection = c; io->input = apr_brigade_create(c->pool, c->bucket_alloc); io->output = apr_brigade_create(c->pool, c->bucket_alloc); io->buflen = 0; io->is_tls = h2_h2_is_tls(c); io->buffer_output = io->is_tls; if (io->buffer_output) { io->bufsize = WRITE_BUFFER_SIZE; io->buffer = apr_pcalloc(c->pool, io->bufsize); } else { io->bufsize = 0; } if (io->is_tls) { /* That is where we start with, * see https://issues.apache.org/jira/browse/TS-2503 */ io->warmup_size = h2_config_geti64(cfg, H2_CONF_TLS_WARMUP_SIZE); io->cooldown_usecs = (h2_config_geti(cfg, H2_CONF_TLS_COOLDOWN_SECS) * APR_USEC_PER_SEC); io->write_size = WRITE_SIZE_INITIAL; } else { io->warmup_size = 0; io->cooldown_usecs = 0; io->write_size = (int)io->bufsize; } if (APLOGctrace1(c)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, io->connection, "h2_conn_io(%ld): init, buffering=%d, warmup_size=%ld, cd_secs=%f", io->connection->id, io->buffer_output, (long)io->warmup_size, ((float)io->cooldown_usecs/APR_USEC_PER_SEC)); } return APR_SUCCESS; }
static int h2_protocol_propose(conn_rec *c, request_rec *r, server_rec *s, const apr_array_header_t *offers, apr_array_header_t *proposals) { int proposed = 0; int is_tls = h2_h2_is_tls(c); const char **protos = is_tls? h2_tls_protos : h2_clear_protos; (void)s; if (strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c))) { /* We do not know how to switch from anything else but http/1.1. */ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "protocol switch: current proto != http/1.1, declined"); return DECLINED; } if (!h2_is_acceptable_connection(c, 0)) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "protocol propose: connection requirements not met"); return DECLINED; } if (r) { /* So far, this indicates an HTTP/1 Upgrade header initiated * protocol switch. For that, the HTTP2-Settings header needs * to be present and valid for the connection. */ const char *p; if (!h2_allows_h2_upgrade(c)) { return DECLINED; } p = apr_table_get(r->headers_in, "HTTP2-Settings"); if (!p) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "upgrade without HTTP2-Settings declined"); return DECLINED; } p = apr_table_get(r->headers_in, "Connection"); if (!ap_find_token(r->pool, p, "http2-settings")) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "upgrade without HTTP2-Settings declined"); return DECLINED; } /* We also allow switching only for requests that have no body. */ p = apr_table_get(r->headers_in, "Content-Length"); if (p && strcmp(p, "0")) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "upgrade with content-length: %s, declined", p); return DECLINED; } } while (*protos) { /* Add all protocols we know (tls or clear) and that * are part of the offerings (if there have been any). */ if (!offers || ap_array_str_contains(offers, *protos)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "proposing protocol '%s'", *protos); APR_ARRAY_PUSH(proposals, const char*) = *protos; proposed = 1; } ++protos; } return proposed? DECLINED : OK; }
int h2_h2_process_conn(conn_rec* c) { apr_status_t status; h2_ctx *ctx; if (c->master) { return DECLINED; } ctx = h2_ctx_get(c, 0); ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn"); if (h2_ctx_is_task(ctx)) { /* our stream pseudo connection */ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "h2_h2, task, declined"); return DECLINED; } if (!ctx && c->keepalives == 0) { const char *proto = ap_get_protocol(c); if (APLOGctrace1(c)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn, " "new connection using protocol '%s', direct=%d, " "tls acceptable=%d", proto, h2_allows_h2_direct(c), h2_is_acceptable_connection(c, 1)); } if (!strcmp(AP_PROTOCOL_HTTP1, proto) && h2_allows_h2_direct(c) && h2_is_acceptable_connection(c, 1)) { /* Fresh connection still is on http/1.1 and H2Direct is enabled. * Otherwise connection is in a fully acceptable state. * -> peek at the first 24 incoming bytes */ apr_bucket_brigade *temp; char *s = NULL; apr_size_t slen; temp = apr_brigade_create(c->pool, c->bucket_alloc); status = ap_get_brigade(c->input_filters, temp, AP_MODE_SPECULATIVE, APR_BLOCK_READ, 24); if (status != APR_SUCCESS) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, APLOGNO(03054) "h2_h2, error reading 24 bytes speculative"); apr_brigade_destroy(temp); return DECLINED; } apr_brigade_pflatten(temp, &s, &slen, c->pool); if ((slen >= 24) && !memcmp(H2_MAGIC_TOKEN, s, 24)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, direct mode detected"); if (!ctx) { ctx = h2_ctx_get(c, 1); } h2_ctx_protocol_set(ctx, h2_h2_is_tls(c)? "h2" : "h2c"); } else { ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "h2_h2, not detected in %d bytes: %s", (int)slen, s); } apr_brigade_destroy(temp); } } if (ctx) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "process_conn"); if (!h2_ctx_session_get(ctx)) { status = h2_conn_setup(ctx, c, NULL); ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c, "conn_setup"); if (status != APR_SUCCESS) { h2_ctx_clear(c); return status; } } return h2_conn_run(ctx, c); } ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, declined"); return DECLINED; }
int h2_h2_process_conn(conn_rec* c) { h2_ctx *ctx = h2_ctx_get(c); ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn"); if (h2_ctx_is_task(ctx)) { /* our stream pseudo connection */ return DECLINED; } if (h2_ctx_protocol_get(c)) { /* Something has been negotiated */ } else if (!strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c)) && h2_allows_h2_direct(c) && h2_is_acceptable_connection(c, 1)) { /* connection still is on http/1.1 and H2Direct is enabled. * Otherwise connection is in a fully acceptable state. * -> peek at the first 24 incoming bytes */ apr_bucket_brigade *temp; apr_status_t status; char *s = NULL; apr_size_t slen; temp = apr_brigade_create(c->pool, c->bucket_alloc); status = ap_get_brigade(c->input_filters, temp, AP_MODE_SPECULATIVE, APR_BLOCK_READ, 24); if (status != APR_SUCCESS) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, "h2_h2, error reading 24 bytes speculative"); apr_brigade_destroy(temp); return DECLINED; } apr_brigade_pflatten(temp, &s, &slen, c->pool); if ((slen >= 24) && !memcmp(H2_MAGIC_TOKEN, s, 24)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, direct mode detected"); h2_ctx_protocol_set(ctx, h2_h2_is_tls(c)? "h2" : "h2c"); } else { ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "h2_h2, not detected in %d bytes: %s", (int)slen, s); } apr_brigade_destroy(temp); } else { /* the connection is not HTTP/1.1 or not for us, don't touch it */ return DECLINED; } /* If "h2" was selected as protocol (by whatever mechanism), take over * the connection. */ if (h2_ctx_is_active(ctx)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, connection, h2 active"); return h2_conn_process(c, NULL, ctx->server); } ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, declined"); return DECLINED; }