Example #1
0
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));
}
Example #2
0
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;
}
Example #3
0
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;
}
Example #4
0
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;
}
Example #5
0
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;
}
Example #6
0
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));
}
Example #7
0
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;
}
Example #8
0
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;
}
Example #9
0
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;
}
Example #10
0
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;
}
Example #11
0
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;
}
Example #12
0
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;
}