diff --git a/src/shrpx.cc b/src/shrpx.cc index 8924d017..4ff3c0d8 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -435,7 +435,6 @@ void fill_default_config() mod_config()->worker_read_burst = 0; mod_config()->worker_write_rate = 0; mod_config()->worker_write_burst = 0; - mod_config()->npn_list = nullptr; mod_config()->verify_client = false; mod_config()->verify_client_cacert = nullptr; mod_config()->client_private_key_file = nullptr; @@ -452,6 +451,8 @@ void fill_default_config() (mod_config()->http2_option, 1); nghttp2_option_set_no_auto_connection_window_update (mod_config()->http2_option, 1); + + mod_config()->tls_proto_mask = 0; } } // namespace @@ -1188,15 +1189,17 @@ int main(int argc, char **argv) } } - if(!get_config()->npn_list) { - mod_config()->npn_list = parse_config_str_list(&mod_config()->npn_list_len, - DEFAULT_NPN_LIST).release(); + if(get_config()->npn_list.empty()) { + mod_config()->npn_list = parse_config_str_list(DEFAULT_NPN_LIST); } - if(!get_config()->tls_proto_list) { + if(get_config()->tls_proto_list.empty()) { mod_config()->tls_proto_list = parse_config_str_list - (&mod_config()->tls_proto_list_len, DEFAULT_TLS_PROTO_LIST).release(); + (DEFAULT_TLS_PROTO_LIST); } + mod_config()->tls_proto_mask = + ssl::create_tls_proto_mask(get_config()->tls_proto_list); + if(!get_config()->subcerts.empty()) { mod_config()->cert_tree = ssl::cert_lookup_tree_new(); } diff --git a/src/shrpx_client_handler.cc b/src/shrpx_client_handler.cc index d7a11760..d4f97f09 100644 --- a/src/shrpx_client_handler.cc +++ b/src/shrpx_client_handler.cc @@ -384,7 +384,6 @@ int ClientHandler::validate_next_proto() CLOG(INFO, this) << "The negotiated next protocol: " << proto; } if(!ssl::in_proto_list(get_config()->npn_list, - get_config()->npn_list_len, next_proto, next_proto_len)) { break; } diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index 3c0bbe1f..11ceda1c 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -238,12 +238,12 @@ std::unique_ptr strcopy(const std::string& val) return res; } -std::unique_ptr parse_config_str_list(size_t *outlen, const char *s) +std::vector parse_config_str_list(const char *s) { size_t len = 1; for(const char *first = s, *p = nullptr; (p = strchr(first, ',')); ++len, first = p + 1); - auto list = util::make_unique(len); + auto list = std::vector(len); auto first = strdup(s); len = 0; for(;;) { @@ -256,10 +256,20 @@ std::unique_ptr parse_config_str_list(size_t *outlen, const char *s) first = p + 1; } list[len++] = first; - *outlen = len; + return list; } +void clear_config_str_list(std::vector& list) +{ + if(list.empty()) { + return; + } + + free(list[0]); + list.clear(); +} + std::pair parse_header(const char *optarg) { // We skip possible ":" at the start of optarg. @@ -490,13 +500,13 @@ int parse_config(const char *opt, const char *optarg) } else if(util::strieq(opt, SHRPX_OPT_WORKER_WRITE_BURST)) { mod_config()->worker_write_burst = strtoul(optarg, nullptr, 10); } else if(util::strieq(opt, SHRPX_OPT_NPN_LIST)) { - delete [] mod_config()->npn_list; - mod_config()->npn_list = parse_config_str_list(&mod_config()->npn_list_len, - optarg).release(); + clear_config_str_list(mod_config()->npn_list); + + mod_config()->npn_list = parse_config_str_list(optarg); } else if(util::strieq(opt, SHRPX_OPT_TLS_PROTO_LIST)) { - delete [] mod_config()->tls_proto_list; - mod_config()->tls_proto_list = parse_config_str_list - (&mod_config()->tls_proto_list_len, optarg).release(); + clear_config_str_list(mod_config()->tls_proto_list); + + mod_config()->tls_proto_list = parse_config_str_list(optarg); } else if(util::strieq(opt, SHRPX_OPT_VERIFY_CLIENT)) { mod_config()->verify_client = util::strieq(optarg, "yes"); } else if(util::strieq(opt, SHRPX_OPT_VERIFY_CLIENT_CACERT)) { @@ -524,17 +534,15 @@ int parse_config(const char *opt, const char *optarg) } else if(util::strieq(opt, SHRPX_OPT_PADDING)) { mod_config()->padding = strtoul(optarg, nullptr, 10); } else if(util::strieq(opt, SHRPX_OPT_ALTSVC)) { - size_t len; + auto tokens = parse_config_str_list(optarg); - auto tokens = parse_config_str_list(&len, optarg); - - if(len < 2) { + if(tokens.size() < 2) { // Requires at least protocol_id and port LOG(ERROR) << "altsvc: too few parameters: " << optarg; return -1; } - if(len > 4) { + if(tokens.size() > 4) { // We only need protocol_id, port, host and origin LOG(ERROR) << "altsvc: too many parameters: " << optarg; return -1; @@ -555,11 +563,11 @@ int parse_config(const char *opt, const char *optarg) altsvc.protocol_id = tokens[0]; altsvc.protocol_id_len = strlen(altsvc.protocol_id); - if(len > 2) { + if(tokens.size() > 2) { altsvc.host = tokens[2]; altsvc.host_len = strlen(altsvc.host); - if(len > 3) { + if(tokens.size() > 3) { altsvc.origin = tokens[3]; altsvc.origin_len = strlen(altsvc.origin); } diff --git a/src/shrpx_config.h b/src/shrpx_config.h index 2d556cf0..06243978 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -188,10 +188,10 @@ struct Config { // list of supported NPN/ALPN protocol strings in the order of // preference. The each element of this list is a NULL-terminated // string. - char **npn_list; + std::vector npn_list; // list of supported SSL/TLS protocol strings. The each element of // this list is a NULL-terminated string. - char **tls_proto_list; + std::vector tls_proto_list; // Path to file containing CA certificate solely used for client // certificate validation std::unique_ptr verify_client_cacert; @@ -217,11 +217,10 @@ struct Config { size_t worker_read_burst; size_t worker_write_rate; size_t worker_write_burst; - // The number of elements in npn_list - size_t npn_list_len; - // The number of elements in tls_proto_list - size_t tls_proto_list_len; size_t padding; + // Bit mask to disable SSL/TLS protocol versions. This will be + // passed to SSL_CTX_set_options(). + long int tls_proto_mask; // downstream protocol; this will be determined by given options. shrpx_proto downstream_proto; int syslog_facility; @@ -278,14 +277,18 @@ std::string read_passwd_from_file(const char *filename); // Parses comma delimited strings in |s| and returns the array of // pointers, each element points to the each substring in |s|. The -// number of elements are stored in |*outlen|. The |s| must be comma -// delimited list of strings. The strings must be delimited by a -// single comma and any white spaces around it are treated as a part -// of protocol strings. This function may modify |s| and the caller -// must leave it as is after this call. This function copies |s| and -// first element in the return value points to it. It is caller's -// responsibility to deallocate its memory. -std::unique_ptr parse_config_str_list(size_t *outlen, const char *s); +// |s| must be comma delimited list of strings. The strings must be +// delimited by a single comma and any white spaces around it are +// treated as a part of protocol strings. This function may modify +// |s| and the caller must leave it as is after this call. This +// function copies |s| and first element in the return value points to +// it. It is caller's responsibility to deallocate its memory. +std::vector parse_config_str_list(const char *s); + +// Clears all elements of |list|, which is returned by +// parse_config_str_list(). If list is not empty, list[0] is freed by +// free(2). After this call, list.empty() must be true. +void clear_config_str_list(std::vector& list); // Parses header field in |optarg|. We expect header field is formed // like "NAME: VALUE". We require that NAME is non empty string. ":" diff --git a/src/shrpx_config_test.cc b/src/shrpx_config_test.cc index 37d68b6c..cfe95c4e 100644 --- a/src/shrpx_config_test.cc +++ b/src/shrpx_config_test.cc @@ -32,32 +32,36 @@ namespace shrpx { void test_shrpx_config_parse_config_str_list(void) { - size_t outlen; - auto res = parse_config_str_list(&outlen, "a"); - CU_ASSERT(1 == outlen); + auto res = parse_config_str_list("a"); + CU_ASSERT(1 == res.size()); CU_ASSERT(0 == strcmp("a", res[0])); + clear_config_str_list(res); - res = parse_config_str_list(&outlen, "a,"); - CU_ASSERT(2 == outlen); + res = parse_config_str_list("a,"); + CU_ASSERT(2 == res.size()); CU_ASSERT(0 == strcmp("a", res[0])); CU_ASSERT(0 == strcmp("", res[1])); + clear_config_str_list(res); - res = parse_config_str_list(&outlen, ",a,,"); - CU_ASSERT(4 == outlen); + res = parse_config_str_list(",a,,"); + CU_ASSERT(4 == res.size()); CU_ASSERT(0 == strcmp("", res[0])); CU_ASSERT(0 == strcmp("a", res[1])); CU_ASSERT(0 == strcmp("", res[2])); CU_ASSERT(0 == strcmp("", res[3])); + clear_config_str_list(res); - res = parse_config_str_list(&outlen, ""); - CU_ASSERT(1 == outlen); + res = parse_config_str_list(""); + CU_ASSERT(1 == res.size()); CU_ASSERT(0 == strcmp("", res[0])); + clear_config_str_list(res); - res = parse_config_str_list(&outlen, "alpha,bravo,charlie"); - CU_ASSERT(3 == outlen); + res = parse_config_str_list("alpha,bravo,charlie"); + CU_ASSERT(3 == res.size()); CU_ASSERT(0 == strcmp("alpha", res[0])); CU_ASSERT(0 == strcmp("bravo", res[1])); CU_ASSERT(0 == strcmp("charlie", res[2])); + clear_config_str_list(res); } void test_shrpx_config_parse_header(void) diff --git a/src/shrpx_ssl.cc b/src/shrpx_ssl.cc index 7a3f2118..186eebeb 100644 --- a/src/shrpx_ssl.cc +++ b/src/shrpx_ssl.cc @@ -88,14 +88,14 @@ int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) } // namespace namespace { -size_t set_npn_prefs(unsigned char *out, char **protos, size_t len) +size_t set_npn_prefs(unsigned char *out, const std::vector& protos) { unsigned char *ptr = out; size_t listlen = 0; - for(size_t i = 0; i < len; ++i) { - size_t plen = strlen(protos[i]); + for(auto proto : protos) { + auto plen = strlen(proto); *ptr = plen; - memcpy(ptr+1, protos[i], *ptr); + memcpy(ptr+1, proto, *ptr); ptr += *ptr+1; listlen += 1 + plen; } @@ -166,11 +166,7 @@ int alpn_select_proto_cb(SSL* ssl, // We assume that get_config()->npn_list contains ALPN protocol // identifier sorted by preference order. So we just break when we // found the first overlap. - for(auto needle_ptr = get_config()->npn_list, - end_needle_ptr = needle_ptr + get_config()->npn_list_len; - needle_ptr < end_needle_ptr; ++needle_ptr) { - - auto target_proto_id = *needle_ptr; + for(auto target_proto_id : get_config()->npn_list) { auto target_proto_len = strlen(reinterpret_cast(target_proto_id)); @@ -206,27 +202,29 @@ int alpn_select_proto_cb(SSL* ssl, #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L namespace { -const char *names[] = { "TLSv1.2", "TLSv1.1", "TLSv1.0", "SSLv3" }; -const size_t namelen = sizeof(names)/sizeof(names[0]); -const long int masks[] = { SSL_OP_NO_TLSv1_2, SSL_OP_NO_TLSv1_1, - SSL_OP_NO_TLSv1, SSL_OP_NO_SSLv3 }; -long int create_tls_proto_mask(char **tls_proto_list, size_t len) +const char *tls_names[] = { "TLSv1.2", "TLSv1.1", "TLSv1.0", "SSLv3" }; +const size_t tls_namelen = sizeof(tls_names)/sizeof(tls_names[0]); +const long int tls_masks[] = { SSL_OP_NO_TLSv1_2, SSL_OP_NO_TLSv1_1, + SSL_OP_NO_TLSv1, SSL_OP_NO_SSLv3 }; +} // namespace + +long int create_tls_proto_mask(const std::vector& tls_proto_list) { long int res = 0; - for(size_t i = 0; i < namelen; ++i) { + + for(size_t i = 0; i < tls_namelen; ++i) { size_t j; - for(j = 0; j < len; ++j) { - if(strcasecmp(names[i], tls_proto_list[j]) == 0) { + for(j = 0; j < tls_proto_list.size(); ++j) { + if(util::strieq(tls_names[i], tls_proto_list[j])) { break; } } - if(j == len) { - res |= masks[i]; + if(j == tls_proto_list.size()) { + res |= tls_masks[i]; } } return res; } -} // namespace SSL_CTX* create_ssl_context(const char *private_key_file, const char *cert_file) @@ -243,8 +241,7 @@ SSL_CTX* create_ssl_context(const char *private_key_file, SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_SINGLE_ECDH_USE | SSL_OP_SINGLE_DH_USE | SSL_OP_NO_TICKET | - create_tls_proto_mask(get_config()->tls_proto_list, - get_config()->tls_proto_list_len)); + get_config()->tls_proto_mask); const unsigned char sid_ctx[] = "shrpx"; SSL_CTX_set_session_id_context(ssl_ctx, sid_ctx, sizeof(sid_ctx)-1); @@ -364,8 +361,8 @@ SSL_CTX* create_ssl_context(const char *private_key_file, SSL_CTX_set_info_callback(ssl_ctx, info_callback); // NPN advertisement - auto proto_list_len = set_npn_prefs(proto_list, get_config()->npn_list, - get_config()->npn_list_len); + auto proto_list_len = set_npn_prefs(proto_list, get_config()->npn_list); + next_proto.first = proto_list; next_proto.second = proto_list_len; SSL_CTX_set_next_protos_advertised_cb(ssl_ctx, next_proto_cb, &next_proto); @@ -401,8 +398,7 @@ SSL_CTX* create_ssl_client_context() SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_COMPRESSION | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | - create_tls_proto_mask(get_config()->tls_proto_list, - get_config()->tls_proto_list_len)); + get_config()->tls_proto_mask); const char *ciphers; if(get_config()->ciphers) { @@ -461,8 +457,8 @@ SSL_CTX* create_ssl_client_context() #if OPENSSL_VERSION_NUMBER >= 0x10002000L // ALPN advertisement - auto proto_list_len = set_npn_prefs(proto_list, get_config()->npn_list, - get_config()->npn_list_len); + auto proto_list_len = set_npn_prefs(proto_list, get_config()->npn_list); + next_proto.first = proto_list; next_proto.second = proto_list_len; SSL_CTX_set_alpn_protos(ssl_ctx, proto_list, proto_list[0] + 1); @@ -900,12 +896,11 @@ int cert_lookup_tree_add_cert_from_file(CertLookupTree *lt, SSL_CTX *ssl_ctx, return 0; } -bool in_proto_list(char **protos, size_t len, - const unsigned char *proto, size_t protolen) +bool in_proto_list(const std::vector& protos, + const unsigned char *needle, size_t len) { - for(size_t i = 0; i < len; ++i) { - if(strlen(protos[i]) == protolen && - memcmp(protos[i], proto, protolen) == 0) { + for(auto proto : protos) { + if(strlen(proto) == len && memcmp(proto, needle, len) == 0) { return true; } } diff --git a/src/shrpx_ssl.h b/src/shrpx_ssl.h index ac6aed87..bbd9e4cc 100644 --- a/src/shrpx_ssl.h +++ b/src/shrpx_ssl.h @@ -123,15 +123,19 @@ SSL_CTX* cert_lookup_tree_lookup(CertLookupTree *lt, const char *hostname, int cert_lookup_tree_add_cert_from_file(CertLookupTree *lt, SSL_CTX *ssl_ctx, const char *certfile); -// Returns true if |proto| which has |protolen| bytes is included in -// the protocol list |protos|, which has |len| elements. The format of -// the |protos| is the one used in Config::npn_list. -bool in_proto_list(char **protos, size_t len, - const unsigned char *proto, size_t protolen); +// Returns true if |needle| which has |len| bytes is included in the +// protocol list |protos|. +bool in_proto_list(const std::vector& protos, + const unsigned char *needle, size_t len); // Returns true if security requirement for HTTP/2 is fulfilled. bool check_http2_requirement(SSL *ssl); +// Returns SSL/TLS option mask to disable SSL/TLS protocol version not +// included in |tls_proto_list|. The returned mask can be directly +// passed to SSL_CTX_set_options(). +long int create_tls_proto_mask(const std::vector& tls_proto_list); + } // namespace ssl } // namespace shrpx