bud_error_t bud_parse_header(const uint8_t* data, size_t size, bud_parser_state_t* state) { bud_error_t err; /* >= 5 + frame size bytes for frame parsing */ if (state->body_offset + state->frame_len > size) return bud_error(kBudErrParserNeedMore); if (data[state->body_offset] == kClientHello) { /* Clear hello, just in case if we will return bud_ok() ;) */ memset(state->hello, 0, sizeof(*state->hello)); err = bud_parse_tls_client_hello(data, size, state); if (!bud_is_ok(err)) return err; /* Check if we overflowed (do not reply with any private data) */ if (state->hello->session == NULL || state->hello->session_len > 32 || state->hello->session + state->hello->session_len > (const char*) data + size) { return bud_error_str(kBudErrParserErr, "Session id overflow"); } } else { return bud_error_str(kBudErrParserErr, "Unexpected first record"); } return bud_ok(); }
bud_error_t bud_parse_record_header(const uint8_t* data, size_t size, bud_parser_state_t* state) { /* >= 5 bytes for header parsing */ if (size < 5) return bud_error(kBudErrParserNeedMore); if (data[0] == kChangeCipherSpec || data[0] == kAlert || data[0] == kHandshake || data[0] == kApplicationData) { state->frame_len = (data[3] << 8) + data[4]; state->body_offset = 5; } else { return bud_error_str(kBudErrParserErr, "Unknown record type"); } /* * Sanity check (too big frame, or too small) * Let OpenSSL handle it */ if (state->frame_len >= kMaxTLSFrameLen) return bud_error_str(kBudErrParserErr, "Record length OOB"); return bud_ok(); }
bud_error_t bud_config_load_backend_list(bud_config_t* config, JSON_Object* obj, bud_config_backend_list_t* backends) { bud_error_t err; JSON_Array* backend; int i; backends->external_count = 0; err = bud_hashmap_init(&backends->external_map, kBudBackendMapSize); if (!bud_is_ok(err)) return err; backend = json_object_get_array(obj, "backend"); backends->count = backend == NULL ? 0 : json_array_get_count(backend); backends->list = calloc(backends->count, sizeof(*backends->list)); if (backends->list == NULL) return bud_error_str(kBudErrNoMem, "bud_backend_list_t"); for (i = 0; i < backends->count; i++) { err = bud_config_load_backend(config, json_array_get_object(backend, i), &backends->list[i], &backends->external_map, &backends->external_count); if (!bud_is_ok(err)) break; } if (!bud_is_ok(err)) { free(backends->list); backends->list = NULL; } return err; }
bud_error_t bud_config_load_ca_arr(X509_STORE** store, const JSON_Array* ca) { int i; int count; bud_error_t err; err = bud_config_verify_all_strings(ca, "ca"); if (!bud_is_ok(err)) return err; *store = X509_STORE_new(); if (*store == NULL) return bud_error_str(kBudErrNoMem, "CA store"); count = json_array_get_count(ca); for (i = 0; i < count; i++) { const char* cert; BIO* b; X509* x509; cert = json_array_get_string(ca, i); b = BIO_new_mem_buf((void*) cert, -1); if (b == NULL) return bud_error_str(kBudErrNoMem, "BIO_new_mem_buf:CA store bio"); while ((x509 = PEM_read_bio_X509(b, NULL, NULL, NULL)) != NULL) { if (x509 == NULL) { err = bud_error_dstr(kBudErrParseCert, cert); break; } if (X509_STORE_add_cert(*store, x509) != 1) { err = bud_error(kBudErrAddCert); break; } X509_free(x509); x509 = NULL; } BIO_free_all(b); if (x509 != NULL) X509_free(x509); } return err; }
char* bud_config_encode_npn(bud_config_t* config, const JSON_Array* npn, size_t* len, bud_error_t* err) { int i; char* npn_line; size_t npn_line_len; unsigned int offset; int npn_count; const char* npn_item; int npn_item_len; /* Try global defaults */ if (npn == NULL) npn = config->frontend.npn; if (npn == NULL) { *err = bud_ok(); *len = 0; return NULL; } /* Calculate storage requirements */ npn_count = json_array_get_count(npn); npn_line_len = 0; for (i = 0; i < npn_count; i++) npn_line_len += 1 + strlen(json_array_get_string(npn, i)); if (npn_line_len != 0) { npn_line = malloc(npn_line_len); if (npn_line == NULL) { *err = bud_error_str(kBudErrNoMem, "NPN copy"); return NULL; } } /* Fill npn line */ for (i = 0, offset = 0; i < npn_count; i++) { npn_item = json_array_get_string(npn, i); npn_item_len = strlen(npn_item); npn_line[offset++] = npn_item_len; memcpy(npn_line + offset, npn_item, npn_item_len); offset += npn_item_len; } ASSERT(offset == npn_line_len, "NPN Line overflow"); *len = npn_line_len; *err = bud_ok(); return npn_line; }
bud_error_t bud_config_load_frontend_ifaces( JSON_Object* obj, bud_config_frontend_interface_t* interface) { JSON_Array* arr; int i; arr = json_object_get_array(obj, "interfaces"); interface->count = arr == NULL ? 0 : json_array_get_count(arr); if (interface->count == 0) return bud_ok(); interface->list = calloc(interface->count, sizeof(*interface->list)); if (interface->list == NULL) return bud_error_str(kBudErrNoMem, "bud_frontend_interface_t"); for (i = 0; i < interface->count; i++) bud_config_load_addr(json_array_get_object(arr, i), &interface->list[i]); return bud_ok(); }
bud_error_t bud_worker(bud_config_t* config) { int r; bud_error_t err; bud_log(config, kBudLogDebug, "worker starting"); config->loop = uv_default_loop(); if (config->loop == NULL) { err = bud_error_str(kBudErrNoMem, "config->loop"); goto fatal; } err = bud_ipc_init(&config->ipc, config); if (!bud_is_ok(err)) goto fatal; config->ipc.client_cb = bud_worker_ipc_client_cb; err = bud_ipc_open(&config->ipc, 0); if (!bud_is_ok(err)) goto failed_ipc_open; err = bud_ipc_start(&config->ipc); if (!bud_is_ok(err)) goto failed_ipc_open; config->signal.sighup = malloc(sizeof(*config->signal.sighup)); if (config->signal.sighup == NULL) { err = bud_error_str(kBudErrNoMem, "config->.sighup"); goto failed_ipc_open; } config->signal.sighup->data = config; r = uv_signal_init(config->loop, config->signal.sighup); if (r != 0) { err = bud_error_num(kBudErrSignalInit, r); goto failed_signal_init; } r = uv_signal_start(config->signal.sighup, bud_worker_signal_cb, SIGHUP); if (r != 0) { err = bud_error_num(kBudErrSignalInit, r); goto failed_signal_start; } #ifndef _WIN32 /* Drop privileges */ err = bud_config_drop_privileges(config); if (!bud_is_ok(err)) goto failed_signal_start; #endif /* !_WIN32 */ err = bud_ok(); return err; failed_signal_start: uv_close((uv_handle_t*) config->signal.sighup, bud_worker_close_cb); goto failed_ipc_open; failed_signal_init: free(config->signal.sighup); config->signal.sighup = NULL; failed_ipc_open: bud_ipc_close(&config->ipc); fatal: return err; }
bud_error_t bud_config_init(bud_config_t* config) { bud_error_t err; int i; int r; /* Get addresses of frontend and backend */ r = bud_config_str_to_addr(config->frontend.host, config->frontend.port, &config->frontend.addr); if (r != 0) return bud_error_num(kBudErrPton, r); for (i = 0; i < config->frontend.interface.count; i++) { bud_config_addr_t* addr; addr = &config->frontend.interface.list[i]; r = bud_config_str_to_addr(addr->host, addr->port, &addr->addr); if (r != 0) return bud_error_num(kBudErrPton, r); } err = bud_config_format_proxyline(config); if (!bud_is_ok(err)) return err; i = 0; config->balance_e = bud_config_balance_to_enum(config->balance); /* At least one backend should be present for non-SNI balancing */ if (config->contexts[0].backend.count == 0 && config->balance_e != kBudBalanceSNI) { err = bud_error(kBudErrNoBackend); goto fatal; } /* Get indexes for SSL_set_ex_data()/SSL_get_ex_data() */ if (kBudSSLClientIndex == -1) { kBudSSLConfigIndex = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL); kBudSSLClientIndex = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); kBudSSLSNIIndex = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); kBudSSLTicketKeyIndex = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); if (kBudSSLConfigIndex == -1 || kBudSSLClientIndex == -1 || kBudSSLSNIIndex == -1 || kBudSSLTicketKeyIndex == -1) { err = bud_error(kBudErrNoSSLIndex); goto fatal; } } #ifndef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB if (config->context_count != 0) { err = bud_error(kBudErrSNINotSupported); goto fatal; } #endif /* !SSL_CTRL_SET_TLSEXT_SERVERNAME_CB */ /* Allocate workers */ if (!config->is_worker && config->worker_count != 0) { config->workers = calloc(config->worker_count, sizeof(*config->workers)); if (config->workers == NULL) { err = bud_error_str(kBudErrNoMem, "workers"); goto fatal; } } /* Initialize logger */ config->logger = bud_logger_new(config, &err); if (!bud_is_ok(err)) goto fatal; err = bud_config_init_tracing(&config->trace); if (!bud_is_ok(err)) goto fatal; if (config->is_worker || config->worker_count == 0) { /* Connect to SNI server */ if (config->sni.enabled) { config->sni.pool = bud_http_pool_new(config, config->sni.host, config->sni.port, &err); if (config->sni.pool == NULL) goto fatal; } /* Connect to OCSP Stapling server */ if (config->stapling.enabled) { config->stapling.pool = bud_http_pool_new(config, config->stapling.host, config->stapling.port, &err); if (config->stapling.pool == NULL) goto fatal; } } /* Init all contexts */ for (i = 0; i < config->context_count + 1; i++) { err = bud_context_init(config, &config->contexts[i]); if (!bud_is_ok(err)) goto fatal; } return bud_ok(); fatal: /* Free all allocated contexts */ do bud_context_free(&config->contexts[i--]); while (i >= 0); return err; }
bud_error_t bud_config_new(int argc, char** argv, bud_config_t** out) { bud_error_t err; bud_config_t* config; int i; int r; size_t path_len; int c; int index; int loaded; config = calloc(1, sizeof(*config)); if (config == NULL) return bud_error_str(kBudErrNoMem, "bud_config_t"); loaded = 0; do { index = 0; c = getopt_long(argc, argv, bud_long_flags, bud_long_options, &index); switch (c) { case 'v': bud_print_version(); err = bud_error(kBudErrSkip); goto fatal; #ifndef _WIN32 case 'd': config->is_daemon = 1; #endif /* !_WIN32 */ break; case 'p': case 'i': case 'c': if (loaded) { err = bud_error(kBudErrMultipleConfigs); goto fatal; } loaded = 1; if (c == 'p') { config->piped = 1; config->path = kPipedConfigPath; } else { config->piped = 0; config->path = optarg; config->inlined = c == 'i'; } break; case 1000: config->is_worker = 1; break; case 1001: bud_config_print_default(); err = bud_error(kBudErrSkip); goto fatal; default: if (loaded) break; bud_print_help(argc, argv); goto no_config; } } while (c != -1); if (!config->piped) { config->piped_index = -1; } else { /* get_opt does not provide the argc offset so must manually retrieve it */ for (i = 0; i < argc; i++) { if (strcmp(argv[i], "--piped-config") == 0 || strcmp(argv[i], "-p") == 0) { config->piped_index = i; break; } } } /* CLI options */ config->argc = argc; config->argv = argv; /* Get executable path */ path_len = sizeof(config->exepath); r = uv_exepath(config->exepath, &path_len); ASSERT(path_len < sizeof(config->exepath), "Exepath OOB"); if (r != 0) { bud_config_free(config); config = NULL; return bud_error_num(kBudErrExePath, r); } err = bud_hashmap_init(&config->files.hashmap, kBudFileCacheSize); if (!bud_is_ok(err)) goto fatal; *out = config; return bud_ok(); no_config: free(config); return bud_error(kBudErrNoConfig); fatal: free(config); return err; }
bud_config_t* bud_config_load(const char* path, int inlined, bud_error_t* err) { int i; JSON_Value* json; JSON_Value* val; JSON_Object* obj; JSON_Object* tmp; JSON_Object* log; JSON_Object* avail; JSON_Array* contexts; JSON_Array* backend; bud_config_t* config; bud_context_t* ctx; if (inlined) json = json_parse_string(path); else json = json_parse_file(path); if (json == NULL) { *err = bud_error_str(kBudErrJSONParse, path); goto end; } obj = json_value_get_object(json); if (obj == NULL) { *err = bud_error(kBudErrJSONNonObjectRoot); goto failed_get_object; } config = calloc(1, sizeof(*config)); if (config == NULL) { *err = bud_error_str(kBudErrNoMem, "bud_config_t"); goto failed_get_object; } /* Copy path or inlined config value */ config->path = strdup(path); if (config->path == NULL) { *err = bud_error_str(kBudErrNoMem, "bud_config_t strcpy(path)"); goto failed_alloc_path; } config->inlined = inlined; /* Allocate contexts and backends */ contexts = json_object_get_array(obj, "contexts"); backend = json_object_get_array(obj, "backend"); config->context_count = contexts == NULL ? 0 : json_array_get_count(contexts); config->backend_count = backend == NULL ? 0 : json_array_get_count(backend); config->contexts = calloc(config->context_count + 1, sizeof(*config->contexts)); config->backend = calloc(config->backend_count, sizeof(*config->backend)); if (config->contexts == NULL || config->backend == NULL) { *err = bud_error_str(kBudErrNoMem, "bud_context_t"); goto failed_get_index; } config->json = json; /* Workers configuration */ config->worker_count = -1; config->restart_timeout = -1; val = json_object_get_value(obj, "workers"); if (val != NULL) config->worker_count = json_value_get_number(val); val = json_object_get_value(obj, "restart_timeout"); if (val != NULL) config->restart_timeout = json_value_get_number(val); /* Logger configuration */ log = json_object_get_object(obj, "log"); config->log.stdio = -1; config->log.syslog = -1; if (log != NULL) { config->log.level = json_object_get_string(log, "level"); config->log.facility = json_object_get_string(log, "facility"); val = json_object_get_value(log, "stdio"); if (val != NULL) config->log.stdio = json_value_get_boolean(val); val = json_object_get_value(log, "syslog"); if (val != NULL) config->log.syslog = json_value_get_boolean(val); } /* Availability configuration */ avail = json_object_get_object(obj, "availability"); config->availability.death_timeout = -1; config->availability.revive_interval = -1; config->availability.retry_interval = -1; config->availability.max_retries = -1; if (avail != NULL) { val = json_object_get_value(avail, "death_timeout"); if (val != NULL) config->availability.death_timeout = json_value_get_number(val); val = json_object_get_value(avail, "revive_interval"); if (val != NULL) config->availability.revive_interval = json_value_get_number(val); val = json_object_get_value(avail, "retry_interval"); if (val != NULL) config->availability.retry_interval = json_value_get_number(val); val = json_object_get_value(avail, "max_retries"); if (val != NULL) config->availability.max_retries = json_value_get_number(val); } /* Frontend configuration */ *err = bud_config_load_frontend(json_object_get_object(obj, "frontend"), &config->frontend); if (!bud_is_ok(*err)) goto failed_get_index; /* Backend configuration */ config->balance = json_object_get_string(obj, "balance"); for (i = 0; i < config->backend_count; i++) { bud_config_load_backend(config, json_array_get_object(backend, i), &config->backend[i]); } /* SNI configuration */ bud_config_read_pool_conf(obj, "sni", &config->sni); /* OCSP Stapling configuration */ bud_config_read_pool_conf(obj, "stapling", &config->stapling); /* SSL Contexts */ /* TODO(indutny): sort them and do binary search */ for (i = 0; i < config->context_count; i++) { /* NOTE: contexts[0] - is a default context */ ctx = &config->contexts[i + 1]; obj = json_array_get_object(contexts, i); if (obj == NULL) { *err = bud_error(kBudErrJSONNonObjectCtx); goto failed_get_index; } ctx->servername = json_object_get_string(obj, "servername"); ctx->servername_len = ctx->servername == NULL ? 0 : strlen(ctx->servername); ctx->cert_file = json_object_get_string(obj, "cert"); ctx->key_file = json_object_get_string(obj, "key"); ctx->npn = json_object_get_array(obj, "npn"); ctx->ciphers = json_object_get_string(obj, "ciphers"); ctx->ecdh = json_object_get_string(obj, "ecdh"); ctx->ticket_key = json_object_get_string(obj, "ticket_key"); tmp = json_object_get_object(obj, "backend"); if (tmp != NULL) { ctx->backend = &ctx->backend_st; bud_config_load_backend(config, tmp, ctx->backend); } *err = bud_config_verify_npn(ctx->npn); if (!bud_is_ok(*err)) goto failed_get_index; } bud_config_set_defaults(config); *err = bud_ok(); return config; failed_get_index: free(config->contexts); config->contexts = NULL; free(config->backend); config->backend = NULL; free(config->path); config->path = NULL; failed_alloc_path: free(config); failed_get_object: json_value_free(json); end: return NULL; }
bud_error_t bud_parse_extension(extension_type_t type, const uint8_t* data, size_t size, bud_parser_state_t* state) { uint32_t server_names_len; size_t offset; uint8_t name_type; uint16_t name_len; switch (type) { case kServername: if (size < 2) return bud_error_str(kBudErrParserErr, "Servername ext is too small"); server_names_len = (data[0] << 8) + data[1]; if (server_names_len + 2 > size) return bud_error_str(kBudErrParserErr, "Servername ext OOB"); for (offset = 2; offset < 2 + server_names_len; ) { if (offset + 3 > size) return bud_error_str(kBudErrParserErr, "Servername name OOB"); name_type = data[offset]; if (name_type != kServernameHostname) return bud_error_str(kBudErrParserErr, "Servername type unexpected"); name_len = (data[offset + 1] << 8) + data[offset + 2]; offset += 3; if (offset + name_len > size) return bud_error_str(kBudErrParserErr, "Servername value OOB"); state->hello->servername = (const char*) data + offset; state->hello->servername_len = name_len; offset += name_len; } break; case kStatusRequest: /* We are ignoring any data, just indicating the presence of extension */ if (size < 5) return bud_error_str(kBudErrParserErr, "StatusRequest is too small"); /* Unknown type, ignore it */ if (data[0] != kStatusRequestOCSP) break; /* TODO(indutny): support extensions */ if (size != 5) break; state->hello->ocsp_request = 1; break; case kTLSSessionTicket: state->hello->ticket_len = size; state->hello->ticket = (const char*) data + size; break; default: /* Ignore */ break; } return bud_ok(); }
bud_error_t bud_config_init(bud_config_t* config) { int i; int r; bud_context_t* ctx; bud_error_t err; const char* cert_file; const char* key_file; BIO* cert_bio; i = 0; /* Get addresses of frontend and backend */ r = bud_config_str_to_addr(config->frontend.host, config->frontend.port, &config->frontend.addr); if (r != 0) { err = bud_error_num(kBudErrPton, r); goto fatal; } r = bud_config_str_to_addr(config->backend.host, config->backend.port, &config->backend.addr); if (r != 0) { err = bud_error_num(kBudErrPton, r); goto fatal; } /* Get indexes for SSL_set_ex_data()/SSL_get_ex_data() */ if (kBudSSLClientIndex == -1) { kBudSSLClientIndex = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); kBudSSLSNIIndex = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); if (kBudSSLClientIndex == -1 || kBudSSLSNIIndex == -1) goto fatal; } #ifndef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB if (config->context_count != 0) { err = bud_error(kBudErrSNINotSupported); goto fatal; } #endif /* !SSL_CTRL_SET_TLSEXT_SERVERNAME_CB */ /* Allocate workers */ if (!config->is_worker) { config->workers = calloc(config->worker_count, sizeof(*config->workers)); if (config->workers == NULL) { err = bud_error_str(kBudErrNoMem, "workers"); goto fatal; } } /* Initialize logger */ err = bud_logger_new(config); if (!bud_is_ok(err)) goto fatal; if (config->is_worker || config->worker_count == 0) { /* Connect to SNI server */ if (config->sni.enabled) { config->sni.pool = bud_http_pool_new(config, config->sni.host, config->sni.port, &err); if (config->sni.pool == NULL) goto fatal; } /* Connect to OCSP Stapling server */ if (config->stapling.enabled) { config->stapling.pool = bud_http_pool_new(config, config->stapling.host, config->stapling.port, &err); if (config->stapling.pool == NULL) goto fatal; } } /* Load all contexts */ for (i = 0; i < config->context_count + 1; i++) { ctx = &config->contexts[i]; err = bud_config_new_ssl_ctx(config, ctx); if (!bud_is_ok(err)) goto fatal; /* Default context */ if (i == 0) { cert_file = config->frontend.cert_file; key_file = config->frontend.key_file; } else { cert_file = ctx->cert_file; key_file = ctx->key_file; } cert_bio = BIO_new_file(cert_file, "r"); if (cert_bio == NULL) { err = bud_error_str(kBudErrLoadCert, cert_file); goto fatal; } r = bud_context_use_certificate_chain(ctx, cert_bio); BIO_free_all(cert_bio); if (!r) { err = bud_error_str(kBudErrParseCert, cert_file); goto fatal; } if (!SSL_CTX_use_PrivateKey_file(ctx->ctx, key_file, SSL_FILETYPE_PEM)) { err = bud_error_str(kBudErrParseKey, key_file); goto fatal; } } return bud_ok(); fatal: /* Free all allocated contexts */ do bud_context_free(&config->contexts[i--]); while (i >= 0); return err; }
bud_error_t bud_config_new_ssl_ctx(bud_config_t* config, bud_context_t* context) { SSL_CTX* ctx; bud_error_t err; int options; /* Choose method, tlsv1_2 by default */ if (config->frontend.method == NULL) { if (strcmp(config->frontend.security, "tls1.1") == 0) config->frontend.method = TLSv1_1_server_method(); else if (strcmp(config->frontend.security, "tls1.0") == 0) config->frontend.method = TLSv1_server_method(); else if (strcmp(config->frontend.security, "tls1.2") == 0) config->frontend.method = TLSv1_2_server_method(); else if (strcmp(config->frontend.security, "ssl3") == 0) config->frontend.method = SSLv3_server_method(); else config->frontend.method = SSLv23_server_method(); } ctx = SSL_CTX_new(config->frontend.method); if (ctx == NULL) return bud_error_str(kBudErrNoMem, "SSL_CTX"); SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER | SSL_SESS_CACHE_NO_INTERNAL | SSL_SESS_CACHE_NO_AUTO_CLEAR); if (context->ciphers != NULL) SSL_CTX_set_cipher_list(ctx, context->ciphers); else if (config->frontend.ciphers != NULL) SSL_CTX_set_cipher_list(ctx, config->frontend.ciphers); /* Disable SSL2 */ options = SSL_OP_NO_SSLv2 | SSL_OP_ALL; if (!config->frontend.ssl3) options |= SSL_OP_NO_SSLv3; if (config->frontend.server_preference) options |= SSL_OP_CIPHER_SERVER_PREFERENCE; SSL_CTX_set_options(ctx, options); #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB if (config->context_count != 0) { SSL_CTX_set_tlsext_servername_callback(ctx, bud_config_select_sni_context); SSL_CTX_set_tlsext_servername_arg(ctx, config); } #endif /* SSL_CTRL_SET_TLSEXT_SERVERNAME_CB */ #ifdef OPENSSL_NPN_NEGOTIATED context->npn_line = bud_config_encode_npn(config, context->npn, &context->npn_line_len, &err); if (!bud_is_ok(err)) goto fatal; if (context->npn_line != NULL) { SSL_CTX_set_next_protos_advertised_cb(ctx, bud_config_advertise_next_proto, context); } #else /* !OPENSSL_NPN_NEGOTIATED */ err = bud_error(kBudErrNPNNotSupported); goto fatal; #endif /* OPENSSL_NPN_NEGOTIATED */ SSL_CTX_set_tlsext_status_cb(ctx, bud_client_stapling_cb); context->ctx = ctx; return bud_ok(); fatal: SSL_CTX_free(ctx); return err; }
bud_config_t* bud_config_load(uv_loop_t* loop, const char* path, bud_error_t* err) { int i; int context_count; JSON_Value* json; JSON_Value* val; JSON_Object* obj; JSON_Object* log; JSON_Object* frontend; JSON_Object* backend; JSON_Array* contexts; bud_config_t* config; bud_context_t* ctx; json = json_parse_file(path); if (json == NULL) { *err = bud_error_str(kBudErrJSONParse, path); goto end; } obj = json_value_get_object(json); if (obj == NULL) { *err = bud_error(kBudErrJSONNonObjectRoot); goto failed_get_object; } contexts = json_object_get_array(obj, "contexts"); context_count = contexts == NULL ? 0 : json_array_get_count(contexts); config = calloc(1, sizeof(*config) + context_count * sizeof(*config->contexts)); if (config == NULL) { *err = bud_error_str(kBudErrNoMem, "bud_config_t"); goto failed_get_object; } config->loop = loop; config->json = json; /* Workers configuration */ config->worker_count = -1; config->restart_timeout = -1; val = json_object_get_value(obj, "workers"); if (val != NULL) config->worker_count = json_value_get_number(val); val = json_object_get_value(obj, "restart_timeout"); if (val != NULL) config->restart_timeout = json_value_get_number(val); /* Logger configuration */ log = json_object_get_object(obj, "log"); config->log.stdio = -1; config->log.syslog = -1; if (log != NULL) { config->log.level = json_object_get_string(log, "level"); config->log.facility = json_object_get_string(log, "facility"); val = json_object_get_value(log, "stdio"); if (val != NULL) config->log.stdio = json_value_get_boolean(val); val = json_object_get_value(log, "syslog"); if (val != NULL) config->log.syslog = json_value_get_boolean(val); } /* Frontend configuration */ frontend = json_object_get_object(obj, "frontend"); config->frontend.proxyline = -1; config->frontend.keepalive = -1; config->frontend.server_preference = -1; config->frontend.ssl3 = -1; if (frontend != NULL) { config->frontend.port = (uint16_t) json_object_get_number(frontend, "port"); config->frontend.host = json_object_get_string(frontend, "host"); config->frontend.security = json_object_get_string(frontend, "security"); config->frontend.npn = json_object_get_array(frontend, "npn"); config->frontend.ciphers = json_object_get_string(frontend, "ciphers"); config->frontend.cert_file = json_object_get_string(frontend, "cert"); config->frontend.key_file = json_object_get_string(frontend, "key"); config->frontend.reneg_window = json_object_get_number(frontend, "reneg_window"); config->frontend.reneg_limit = json_object_get_number(frontend, "reneg_limit"); *err = bud_config_verify_npn(config->frontend.npn); if (!bud_is_ok(*err)) goto failed_get_index; val = json_object_get_value(frontend, "proxyline"); if (val != NULL) config->frontend.proxyline = json_value_get_boolean(val); val = json_object_get_value(frontend, "keepalive"); if (val != NULL) config->frontend.keepalive = json_value_get_number(val); val = json_object_get_value(frontend, "server_preference"); if (val != NULL) config->frontend.server_preference = json_value_get_boolean(val); val = json_object_get_value(frontend, "ssl3"); if (val != NULL) config->frontend.ssl3 = json_value_get_boolean(val); } /* Backend configuration */ backend = json_object_get_object(obj, "backend"); config->backend.keepalive = -1; if (backend != NULL) { config->backend.port = (uint16_t) json_object_get_number(backend, "port"); config->backend.host = json_object_get_string(backend, "host"); val = json_object_get_value(backend, "keepalive"); if (val != NULL) config->backend.keepalive = json_value_get_number(val); } /* SNI configuration */ bud_config_read_pool_conf(obj, "sni", &config->sni); /* OCSP Stapling configuration */ bud_config_read_pool_conf(obj, "stapling", &config->stapling); /* SSL Contexts */ /* TODO(indutny): sort them and do binary search */ for (i = 0; i < context_count; i++) { /* NOTE: contexts[0] - is a default context */ ctx = &config->contexts[i + 1]; obj = json_array_get_object(contexts, i); if (obj == NULL) { *err = bud_error(kBudErrJSONNonObjectCtx); goto failed_get_index; } ctx->servername = json_object_get_string(obj, "servername"); ctx->servername_len = ctx->servername == NULL ? 0 : strlen(ctx->servername); ctx->cert_file = json_object_get_string(obj, "cert"); ctx->key_file = json_object_get_string(obj, "key"); ctx->npn = json_object_get_array(obj, "npn"); ctx->ciphers = json_object_get_string(obj, "ciphers"); *err = bud_config_verify_npn(ctx->npn); if (!bud_is_ok(*err)) goto failed_get_index; } config->context_count = context_count; bud_config_set_defaults(config); *err = bud_ok(); return config; failed_get_index: free(config); failed_get_object: json_value_free(json); end: return NULL; }
bud_error_t bud_config_new_ssl_ctx(bud_config_t* config, bud_context_t* context) { SSL_CTX* ctx; int ecdh_nid; EC_KEY* ecdh; bud_error_t err; int options; int r; const char* ticket_key; size_t max_len; if (context->backend != NULL) { if (context->backend->keepalive == -1) context->backend->keepalive = kBudDefaultKeepalive; r = bud_config_str_to_addr(context->backend->host, context->backend->port, &context->backend->addr); if (r != 0) return bud_error_num(kBudErrPton, r); } /* Decode ticket_key */ ticket_key = context->ticket_key == NULL ? config->frontend.ticket_key : context->ticket_key; if (ticket_key != NULL) { max_len = sizeof(context->ticket_key_storage); if (bud_base64_decode(context->ticket_key_storage, max_len, ticket_key, strlen(ticket_key)) < max_len) { return bud_error(kBudErrSmallTicketKey); } } /* Choose method, tlsv1_2 by default */ if (config->frontend.method == NULL) { if (strcmp(config->frontend.security, "tls1.1") == 0) config->frontend.method = TLSv1_1_server_method(); else if (strcmp(config->frontend.security, "tls1.0") == 0) config->frontend.method = TLSv1_server_method(); else if (strcmp(config->frontend.security, "tls1.2") == 0) config->frontend.method = TLSv1_2_server_method(); else if (strcmp(config->frontend.security, "ssl3") == 0) config->frontend.method = SSLv3_server_method(); else config->frontend.method = SSLv23_server_method(); } ctx = SSL_CTX_new(config->frontend.method); if (ctx == NULL) return bud_error_str(kBudErrNoMem, "SSL_CTX"); /* Disable sessions, they won't work with cluster anyway */ SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); if (config->frontend.max_send_fragment) SSL_CTX_set_max_send_fragment(ctx, config->frontend.max_send_fragment); if (ticket_key != NULL) { SSL_CTX_set_tlsext_ticket_keys(ctx, context->ticket_key_storage, sizeof(context->ticket_key_storage)); } /* ECDH curve selection */ if (context->ecdh != NULL || config->frontend.ecdh != NULL) { if (context->ecdh != NULL) ecdh_nid = OBJ_sn2nid(context->ecdh); else ecdh_nid = OBJ_sn2nid(config->frontend.ecdh); if (ecdh_nid == NID_undef) { ecdh = NULL; err = bud_error_str(kBudErrECDHNotFound, context->ecdh == NULL ? config->frontend.ecdh : context->ecdh); goto fatal; } ecdh = EC_KEY_new_by_curve_name(ecdh_nid); if (ecdh == NULL) { err = bud_error_str(kBudErrNoMem, "EC_KEY"); goto fatal; } SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE); SSL_CTX_set_tmp_ecdh(ctx, ecdh); EC_KEY_free(ecdh); } ecdh = NULL; /* Cipher suites */ if (context->ciphers != NULL) SSL_CTX_set_cipher_list(ctx, context->ciphers); else if (config->frontend.ciphers != NULL) SSL_CTX_set_cipher_list(ctx, config->frontend.ciphers); /* Disable SSL2 */ options = SSL_OP_NO_SSLv2 | SSL_OP_ALL; if (!config->frontend.ssl3) options |= SSL_OP_NO_SSLv3; if (config->frontend.server_preference) options |= SSL_OP_CIPHER_SERVER_PREFERENCE; SSL_CTX_set_options(ctx, options); #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB if (config->context_count != 0) { SSL_CTX_set_tlsext_servername_callback(ctx, bud_config_select_sni_context); SSL_CTX_set_tlsext_servername_arg(ctx, config); } #endif /* SSL_CTRL_SET_TLSEXT_SERVERNAME_CB */ #ifdef OPENSSL_NPN_NEGOTIATED context->npn_line = bud_config_encode_npn(config, context->npn, &context->npn_line_len, &err); if (!bud_is_ok(err)) goto fatal; if (context->npn_line != NULL) { SSL_CTX_set_next_protos_advertised_cb(ctx, bud_config_advertise_next_proto, context); } #else /* !OPENSSL_NPN_NEGOTIATED */ err = bud_error(kBudErrNPNNotSupported); goto fatal; #endif /* OPENSSL_NPN_NEGOTIATED */ SSL_CTX_set_tlsext_status_cb(ctx, bud_client_stapling_cb); context->ctx = ctx; return bud_ok(); fatal: if (ecdh != NULL) EC_KEY_free(ecdh); SSL_CTX_free(ctx); return err; }
bud_error_t bud_parse_tls_client_hello(const uint8_t* data, size_t size, bud_parser_state_t* state) { bud_error_t err; const uint8_t* body; size_t session_offset; size_t cipher_offset; uint16_t cipher_len; size_t comp_offset; uint8_t comp_len; size_t extension_offset; size_t ext_off; uint16_t ext_type; uint16_t ext_len; /* Skip frame header, hello header, protocol version and random data */ session_offset = state->body_offset + 4 + 2 + 32; if (session_offset + 1 >= size) return bud_error_str(kBudErrParserErr, "Header OOB"); body = data + session_offset; state->hello->session_len = *body; state->hello->session = (const char*) body + 1; cipher_offset = session_offset + 1 + state->hello->session_len; if (cipher_offset + 1 >= size) return bud_error_str(kBudErrParserErr, "Session OOB"); cipher_len = (data[cipher_offset] << 8) + data[cipher_offset + 1]; comp_offset = cipher_offset + 2 + cipher_len; if (comp_offset > size) return bud_error_str(kBudErrParserErr, "Cipher suite OOB"); comp_len = data[comp_offset]; extension_offset = comp_offset + 1 + comp_len; if (extension_offset > size) return bud_error_str(kBudErrParserErr, "Compression methods OOB"); /* No extensions present */ if (extension_offset == size) return bud_ok(); ext_off = extension_offset + 2; // Parse known extensions while (ext_off < size) { // Extension OOB if (ext_off + 4 > size) return bud_error_str(kBudErrParserErr, "Extension header OOB"); ext_type = (data[ext_off] << 8) + data[ext_off + 1]; ext_len = (data[ext_off + 2] << 8) + data[ext_off + 3]; ext_off += 4; // Extension OOB if (ext_off + ext_len > size) return bud_error_str(kBudErrParserErr, "Extension body OOB"); err = bud_parse_extension((extension_type_t) ext_type, data + ext_off, ext_len, state); if (!bud_is_ok(err)) return err; ext_off += ext_len; } // Extensions OOB failure if (ext_off > size) return bud_error_str(kBudErrParserErr, "Extensions OOB"); return bud_ok(); }
bud_error_t bud_sni_from_json(bud_config_t* config, struct json_value_t* json, bud_context_t* ctx) { JSON_Object* obj; JSON_Value* val; const char* cert_str; const char* key_str; const char* pass_str; JSON_Array* cert_strs; JSON_Array* key_strs; JSON_Array* pass_strs; bud_error_t err; cert_str = NULL; key_str = NULL; pass_str = NULL; cert_strs = NULL; key_strs = NULL; pass_strs = NULL; obj = json_value_get_object(json); val = json_object_get_value(obj, "cert"); if (json_value_get_type(val) == JSONString) cert_str = json_value_get_string(val); else cert_strs = json_value_get_array(val); val = json_object_get_value(obj, "key"); if (json_value_get_type(val) == JSONString) key_str = json_value_get_string(val); else key_strs = json_value_get_array(val); val = json_object_get_value(obj, "passphrase"); if (json_value_get_type(val) == JSONString) pass_str = json_value_get_string(val); else pass_strs = json_value_get_array(val); if (obj == NULL || !((cert_str != NULL && key_str != NULL) || (cert_strs != NULL && key_strs != NULL))) { err = bud_error_str(kBudErrJSONParse, "<SNI Response>"); goto fatal; } /* Load NPN from response */ memset(ctx, 0, sizeof(*ctx)); ctx->cert_file = cert_str; ctx->key_file = key_str; ctx->key_pass = pass_str; ctx->cert_files = cert_strs; ctx->key_files = key_strs; ctx->key_passes = pass_strs; ctx->ciphers = json_object_get_string(obj, "ciphers"); ctx->ecdh = json_object_get_string(obj, "ecdh"); ctx->ticket_key = json_object_get_string(obj, "ticket_key"); ctx->npn = json_object_get_array(obj, "npn"); ctx->ca_array = json_object_get_array(obj, "ca"); val = json_object_get_value(obj, "request_cert"); if (val != NULL) ctx->request_cert = json_value_get_boolean(val); err = bud_config_load_backend_list(config, obj, &ctx->backend); if (!bud_is_ok(err)) goto fatal; err = bud_context_init(config, ctx); /* Make sure that deallocated values won't be used */ ctx->cert_file = NULL; ctx->key_file = NULL; ctx->key_pass = NULL; ctx->cert_files = NULL; ctx->key_files = NULL; ctx->key_passes = NULL; if (!bud_is_ok(err)) goto fatal; return bud_ok(); fatal: if (!bud_is_ok(err)) { SSL_CTX_free(ctx->ctx); ctx->ctx = NULL; } free(ctx->backend.list); ctx->backend.list = NULL; return err; }
bud_error_t bud_config_load(bud_config_t* config) { int i; bud_error_t err; JSON_Value* json; JSON_Value* val; JSON_Object* frontend; JSON_Object* obj; JSON_Object* log; JSON_Object* avail; JSON_Array* contexts; if (config->piped) { char* content; ASSERT(config->loop != NULL, "Loop should be present"); err = bud_read_file_by_fd(config->loop, 0, &content); if (!bud_is_ok(err)) goto end; err = bud_hashmap_insert(&config->files.hashmap, kPipedConfigPath, strlen(kPipedConfigPath), content); if (!bud_is_ok(err)) { free(content); goto end; } json = json_parse_string(content); } else if (config->inlined) { json = json_parse_string(config->path); } else { const char* contents; err = bud_config_load_file(config, config->path, &contents); if (!bud_is_ok(err)) goto end; json = json_parse_string(contents); } if (json == NULL) { err = bud_error_dstr(kBudErrJSONParse, config->path); goto end; } obj = json_value_get_object(json); if (obj == NULL) { err = bud_error(kBudErrJSONNonObjectRoot); goto failed_alloc_path; } err = bud_config_load_tracing(&config->trace, json_object_get_object(obj, "tracing")); if (!bud_is_ok(err)) goto failed_alloc_path; /* Allocate contexts and backends */ contexts = json_object_get_array(obj, "contexts"); config->context_count = contexts == NULL ? 0 : json_array_get_count(contexts); config->contexts = calloc(config->context_count + 1, sizeof(*config->contexts)); if (config->contexts == NULL) { err = bud_error_str(kBudErrNoMem, "bud_context_t"); goto failed_alloc_contexts; } config->json = json; /* Workers configuration */ config->worker_count = -1; config->restart_timeout = -1; config->master_ipc = -1; val = json_object_get_value(obj, "workers"); if (val != NULL) config->worker_count = json_value_get_number(val); val = json_object_get_value(obj, "restart_timeout"); if (val != NULL) config->restart_timeout = json_value_get_number(val); val = json_object_get_value(obj, "master_ipc"); if (val != NULL) config->master_ipc = json_value_get_boolean(val); /* Logger configuration */ log = json_object_get_object(obj, "log"); config->log.stdio = -1; config->log.syslog = -1; if (log != NULL) { config->log.level = json_object_get_string(log, "level"); config->log.facility = json_object_get_string(log, "facility"); val = json_object_get_value(log, "stdio"); if (val != NULL) config->log.stdio = json_value_get_boolean(val); val = json_object_get_value(log, "syslog"); if (val != NULL) config->log.syslog = json_value_get_boolean(val); } /* Availability configuration */ avail = json_object_get_object(obj, "availability"); config->availability.death_timeout = -1; config->availability.revive_interval = -1; config->availability.retry_interval = -1; config->availability.max_retries = -1; if (avail != NULL) { val = json_object_get_value(avail, "death_timeout"); if (val != NULL) config->availability.death_timeout = json_value_get_number(val); val = json_object_get_value(avail, "revive_interval"); if (val != NULL) config->availability.revive_interval = json_value_get_number(val); val = json_object_get_value(avail, "retry_interval"); if (val != NULL) config->availability.retry_interval = json_value_get_number(val); val = json_object_get_value(avail, "max_retries"); if (val != NULL) config->availability.max_retries = json_value_get_number(val); } /* Frontend configuration */ frontend = json_object_get_object(obj, "frontend"); err = bud_config_load_frontend(frontend, &config->frontend); if (!bud_is_ok(err)) goto failed_alloc_contexts; /* Load frontend's context */ err = bud_context_load(frontend, &config->contexts[0]); if (!bud_is_ok(err)) goto failed_alloc_contexts; /* Backend configuration */ config->balance = json_object_get_string(obj, "balance"); err = bud_config_load_backend_list(config, obj, &config->contexts[0].backend); if (!bud_is_ok(err)) goto failed_alloc_contexts; /* User and group configuration */ config->user = json_object_get_string(obj, "user"); config->group = json_object_get_string(obj, "group"); /* SNI configuration */ bud_config_read_pool_conf(obj, "sni", &config->sni); /* OCSP Stapling configuration */ bud_config_read_pool_conf(obj, "stapling", &config->stapling); /* SSL Contexts */ /* TODO(indutny): sort them and do binary search */ for (i = 0; i < config->context_count; i++) { bud_context_t* ctx; /* NOTE: contexts[0] - is a default context */ ctx = &config->contexts[i + 1]; obj = json_array_get_object(contexts, i); if (obj == NULL) { err = bud_error(kBudErrJSONNonObjectCtx); goto failed_load_context; } err = bud_context_load(obj, ctx); if (!bud_is_ok(err)) goto failed_load_context; err = bud_config_load_backend_list(config, obj, &ctx->backend); if (!bud_is_ok(err)) goto failed_load_context; } bud_config_set_defaults(config); return bud_config_init(config); failed_load_context: /* Deinitalize contexts */ for (i++; i >= 0; i--) { bud_context_t* ctx; ctx = &config->contexts[i]; free(ctx->backend.list); ctx->backend.list = NULL; } failed_alloc_contexts: free(config->contexts); config->contexts = NULL; free(config->trace.dso); config->trace.dso = NULL; failed_alloc_path: json_value_free(json); config->json = NULL; end: return err; }
bud_error_t bud_worker(bud_config_t* config) { int r; bud_error_t err; bud_log(config, kBudLogDebug, "worker starting"); config->loop = uv_default_loop(); config->ipc = malloc(sizeof(*config->ipc)); config->signal.sighup = malloc(sizeof(*config->signal.sighup)); if (config->ipc == NULL || config->signal.sighup == NULL) { err = bud_error_str(kBudErrNoMem, "config->ipc"); goto fatal; } config->ipc->data = config; config->signal.sighup->data = config; r = uv_pipe_init(config->loop, config->ipc, 1); if (r != 0) { err = bud_error_num(kBudErrIPCPipeInit, r); goto fatal; } r = uv_pipe_open(config->ipc, 0); if (r != 0) { err = bud_error_num(kBudErrIPCPipeOpen, r); goto failed_pipe_open; } r = uv_read_start((uv_stream_t*) config->ipc, bud_worker_alloc_cb, bud_worker_read_cb); if (r != 0) { err = bud_error_num(kBudErrIPCReadStart, r); goto failed_pipe_open; } #ifndef _WIN32 /* Drop privileges */ err = bud_config_drop_privileges(config); if (!bud_is_ok(err)) goto failed_pipe_open; r = uv_signal_init(config->loop, config->signal.sighup); if (r != 0) { err = bud_error_num(kBudErrSignalInit, r); goto failed_pipe_open; } r = uv_signal_start(config->signal.sighup, bud_worker_signal_cb, SIGHUP); if (r != 0) { err = bud_error_num(kBudErrSignalInit, r); goto failed_signal_start; } #endif /* !_WIN32 */ err = bud_ok(); return err; #ifndef _WIN32 failed_signal_start: uv_close((uv_handle_t*) config->signal.sighup, bud_worker_close_cb); #endif /* !_WIN32 */ failed_pipe_open: uv_close((uv_handle_t*) config->ipc, bud_worker_close_cb); goto cleanup; fatal: free(config->ipc); cleanup: config->ipc = NULL; return err; }