bud_error_t bud_context_set_ticket(bud_context_t* context, const char* ticket, size_t size, bud_encoding_t enc) { bud_config_t* config; bud_context_t* root; size_t max_len; int i; config = context->config; root = &config->contexts[0]; if (enc == kBudEncodingRaw) { if (size != sizeof(context->ticket_key_storage)) return bud_error(kBudErrSmallTicketKey); memcpy(context->ticket_key_storage, ticket, size); } else { ASSERT(enc == kBudEncodingBase64, "Unexpected encoding of ticket key"); max_len = sizeof(context->ticket_key_storage); if (bud_base64_decode(context->ticket_key_storage, max_len, ticket, size) < max_len) { return bud_error(kBudErrSmallTicketKey); } } context->ticket_key_on = 1; if (context->ctx != NULL) { SSL_CTX_set_tlsext_ticket_keys(context->ctx, context->ticket_key_storage, sizeof(context->ticket_key_storage)); } if (context != root) return bud_ok(); /* Update ticket key in dependent contexts */ for (i = 0; i < config->context_count + 1; i++) { bud_context_t* cur; cur = &config->contexts[i]; if (cur->ticket_key_on || cur->ctx == NULL) continue; SSL_CTX_set_tlsext_ticket_keys(cur->ctx, cur->ticket_key_storage, sizeof(cur->ticket_key_storage)); } 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_client_error_t bud_client_spdy_xforward(bud_client_t* client, const char* protocol, unsigned int protocol_len) { int major; int minor; int r; unsigned char frame[256]; /* Detect protocol version */ major = -1; minor = 0; switch (protocol_len) { case 1: if (protocol[0] == '3') major = 3; else if (protocol[0] == '2') major = 2; break; case 3: if (strncmp(protocol, "3.1", protocol_len) == 0) { major = 3; minor = 1; } break; default: break; } /* We are done by now */ client->xforward.crlf = 2; if (major == -1) goto skip; assert(12 + client->host_len <= sizeof(frame)); frame[0] = 0x80; frame[1] = major; *(uint16_t*) (frame + 2) = ntohs(kSpdyXForwardFrameType); /* Frame and Host lengths */ *(uint32_t*) (frame + 4) = htonl(4 + client->host_len); *(uint32_t*) (frame + 8) = htonl(client->host_len); /* Copy hostname */ memcpy(frame + 12, client->host, client->host_len); /* Prepend it to output data */ r = ringbuffer_insert(&client->backend.output, 0, (const char*) frame, (size_t) 12 + client->host_len); if (r != 0) { return bud_client_error(bud_error(kBudErrClientXForwardInsert), &client->backend); } skip: return bud_client_ok(&client->backend); }
bud_error_t bud_config_format_proxyline(bud_config_t* config) { int r; char host[INET6_ADDRSTRLEN]; struct sockaddr_in* addr4; struct sockaddr_in6* addr6; addr4 = (struct sockaddr_in*) &config->frontend.addr; addr6 = (struct sockaddr_in6*) &config->frontend.addr; if (config->frontend.addr.ss_family == AF_INET) r = uv_inet_ntop(AF_INET, &addr4->sin_addr, host, sizeof(host)); else r = uv_inet_ntop(AF_INET6, &addr6->sin6_addr, host, sizeof(host)); if (r != 0) return bud_error(kBudErrNtop); r = snprintf(config->proxyline_fmt, sizeof(config->proxyline_fmt), "PROXY %%s %%s %s %%hu %hu\r\n", host, config->frontend.port); ASSERT(r < (int) sizeof(config->proxyline_fmt), "Proxyline format overflowed"); return bud_ok(); }
void bud_client_handshake_start_cb(const SSL* ssl) { bud_client_t* client; uint64_t now; uint64_t limit; client = SSL_get_ex_data(ssl, kBudSSLClientIndex); now = uv_now(client->config->loop); /* NOTE: config's limit is in ms */ limit = (uint64_t) client->config->frontend.reneg_window * 1000; if (now - client->last_handshake > limit) client->handshakes = 0; /* First handshake */ if (client->last_handshake == 0) goto end; DBG(&client->frontend, "renegotation %d", client->handshakes); /* Too many renegotiations in a small time window */ if (++client->handshakes > client->config->frontend.reneg_limit) { bud_client_close( client, bud_client_error(bud_error(kBudErrClientRenegotiationAttack), &client->frontend)); } end: client->last_handshake = now; }
void bud_client_handshake_done_cb(const SSL* ssl) { bud_client_t* client; bud_context_t* context; bud_client_error_t cerr; client = SSL_get_ex_data(ssl, kBudSSLClientIndex); context = SSL_get_ex_data(ssl, kBudSSLSNIIndex); bud_trace_handshake(client); if (client->selected_backend != NULL) return; if (client->config->balance_e != kBudBalanceSNI) return; if (context != NULL) client->selected_backend = context->backend; if (client->selected_backend != NULL) { /* Backend provided - connect */ cerr = bud_client_connect(client); } else { /* No backend in SNI response */ cerr = bud_client_error(bud_error(kBudErrClientNoBackendInSNI), &client->frontend); } if (!bud_is_ok(cerr.err)) bud_client_close(client, cerr); }
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_client_error_t bud_client_throttle(bud_client_t* client, bud_client_side_t* side, ringbuffer* buf) { int err; bud_client_side_t* opposite; if (!ringbuffer_is_full(buf)) return bud_client_ok(side); opposite = side == &client->frontend ? &client->backend : &client->frontend; if (opposite->reading != kBudProgressRunning) goto done; DBG(opposite, "throttle, buffer full: %ld", ringbuffer_size(buf)); err = uv_read_stop((uv_stream_t*) &opposite->tcp); if (err != 0) { NOTICE(opposite, "uv_read_stop failed: %d - \"%s\"", err, uv_strerror(err)); return bud_client_error(bud_error_num(kBudErrClientReadStop, err), side); } opposite->reading = kBudProgressNone; done: return bud_client_error(bud_error(kBudErrClientThrottle), side); }
bud_client_error_t bud_client_http_xforward(bud_client_t* client) { char* out; size_t avail; size_t off; char xforward[256]; int r; out = ringbuffer_read_next(&client->backend.output, &avail); /* Not enough data yet */ if (avail <= client->xforward.skip) goto done; /* Find first CRLF */ for (off = client->xforward.skip; off < avail; off++) { static char* crlf = "\r\n"; char cur; cur = out[off]; /* Reset on mismatch */ if (cur != crlf[client->xforward.crlf]) { client->xforward.crlf = 0; continue; } /* Move forward */ if (++client->xforward.crlf == 2) { off++; break; } } client->xforward.skip = off; if (!bud_client_xforward_done(client)) goto done; /* Format header */ r = snprintf(xforward, sizeof(xforward), "X-Forwarded-For: %s\r\n", client->host); /* Shift data and insert xforward header */ r = ringbuffer_insert(&client->backend.output, client->xforward.skip, xforward, (size_t) r); if (r != 0) { return bud_client_error(bud_error(kBudErrClientXForwardInsert), &client->backend); } done: return bud_client_ok(&client->backend); }
bud_error_t bud_client_ocsp_stapling(bud_client_t* client) { bud_config_t* config; bud_context_t* context; bud_error_t err; const char* id; size_t id_size; config = client->config; if (client->sni_ctx.ctx != NULL) { /* Async SNI success */ context = &client->sni_ctx; } else if (client->hello.servername_len != 0) { /* Matching context */ context = bud_config_select_context(config, client->hello.servername, client->hello.servername_len); } else { /* Default context */ context = &config->contexts[0]; } /* Cache context to prevent second search in OpenSSL's callback */ if (!SSL_set_ex_data(client->ssl, kBudSSLSNIIndex, context)) { err = bud_error(kBudErrStaplingSetData); goto fatal; } id = bud_context_get_ocsp_id(context, &id_size); /* Certificate has no OCSP id */ if (id == NULL) return bud_ok(); /* Request backend for cached respose first */ client->stapling_cache_req = bud_http_get(config->stapling.pool, config->stapling.query_fmt, id, id_size, bud_client_stapling_cache_req_cb, &err); client->stapling_cache_req->data = client; if (!bud_is_ok(err)) goto fatal; client->hello_parse = kBudProgressRunning; return bud_ok(); fatal: return err; }
void bud_client_read_cb(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { int r; bud_client_t* client; bud_client_side_t* side; bud_client_side_t* opposite; bud_client_error_t cerr; client = stream->data; side = bud_client_side_by_tcp(client, (uv_tcp_t*) stream); /* Commit data if there was no error */ r = 0; if (nread >= 0) r = ringbuffer_write_append(&side->input, nread); DBG(side, "after read_cb() => %d", nread); /* Handle EOF */ if (nread == UV_EOF) { side->reading = kBudProgressDone; /* Shutdown opposite side */ opposite = side == &client->frontend ? &client->backend : &client->frontend; cerr = bud_client_shutdown(client, opposite); if (!bud_is_ok(cerr.err)) goto done; } /* Try writing out data anyway */ cerr = bud_client_cycle(client); if (!bud_is_ok(cerr.err)) goto done; if ((r != 0 || nread < 0) && nread != UV_EOF) { if (nread < 0) cerr = bud_client_error(bud_error_num(kBudErrClientReadCb, nread), side); else cerr = bud_client_error(bud_error(kBudErrClientWriteAppend), side); /* Unrecoverable socket error, close */ return bud_client_close(client, cerr); } /* If buffer is full - stop reading */ cerr = bud_client_throttle(client, side, &side->input); done: if (!bud_is_ok(cerr.err) && cerr.err.code != kBudErrClientThrottle) bud_client_close(client, cerr); }
bud_client_error_t bud_client_shutdown(bud_client_t* client, bud_client_side_t* side) { int r; bud_client_error_t cerr; /* Ignore if already shutdown or destroyed */ if (side->shutdown != kBudProgressNone || client->close == kBudProgressDone) return bud_client_ok(side); /* Do not shutdown not-connected socket */ if (side == &client->backend && client->connect != kBudProgressDone) return bud_client_error(bud_error(kBudErrClientShutdownNoConn), side); /* Try cycling data to figure out if there is still something to send */ cerr = bud_client_cycle(client); if (!bud_is_ok(cerr.err)) return cerr; /* Not empty, send everything first */ if (!ringbuffer_is_empty(&side->output)) { side->shutdown = kBudProgressRunning; return bud_client_ok(side); } DBG_LN(side, "shutdown"); if (side == &client->frontend) { if (SSL_shutdown(client->ssl) != 1) SSL_shutdown(client->ssl); /* Try writing close_notify */ cerr = bud_client_send(client, &client->frontend); if (!bud_is_ok(cerr.err)) goto fatal; } side->shutdown_req.data = client; r = uv_shutdown(&side->shutdown_req, (uv_stream_t*) &side->tcp, bud_client_shutdown_cb); if (r != 0) { cerr = bud_client_error(bud_error_num(kBudErrClientShutdown, r), side); } else { cerr = bud_client_ok(side); side->shutdown = kBudProgressRunning; } fatal: side->shutdown = kBudProgressDone; return cerr; }
bud_error_t bud_config_verify_npn(const JSON_Array* npn) { int i; int npn_count; if (npn == NULL) return bud_ok(); npn_count = json_array_get_count(npn); for (i = 0; i < npn_count; i++) { if (json_value_get_type(json_array_get_value(npn, i)) == JSONString) continue; return bud_error(kBudErrNPNNonString); } return bud_ok(); }
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; }
bud_error_t bud_config_format_proxyline(bud_config_t* config) { int r; char host[INET6_ADDRSTRLEN]; struct sockaddr_in* addr4; struct sockaddr_in6* addr6; addr4 = (struct sockaddr_in*) &config->frontend.addr; addr6 = (struct sockaddr_in6*) &config->frontend.addr; if (config->frontend.addr.ss_family == AF_INET) r = uv_inet_ntop(AF_INET, &addr4->sin_addr, host, sizeof(host)); else r = uv_inet_ntop(AF_INET6, &addr6->sin6_addr, host, sizeof(host)); if (r != 0) return bud_error(kBudErrNtop); r = snprintf(config->proxyline_fmt.haproxy, sizeof(config->proxyline_fmt.haproxy), "PROXY %%s %%s %s %%hu %hu\r\n", host, config->frontend.port); ASSERT(r < (int) sizeof(config->proxyline_fmt.haproxy), "Proxyline format overflowed"); r = snprintf(config->proxyline_fmt.json, sizeof(config->proxyline_fmt.json), "BUD {\"family\":\"%%s\"," "\"bud\":{\"host\":\"%s\",\"port\":%hu}," "\"peer\":{" "\"host\":\"%%s\"," "\"port\":%%hu," "\"cn\":%%c%%s%%c," "\"dn\":%%c%%s%%c}" "}\r\n", host, config->frontend.port); ASSERT(r < (int) sizeof(config->proxyline_fmt.json), "Proxyline format overflowed"); return bud_ok(); }
bud_error_t bud_config_load_ca_file(X509_STORE** store, const char* filename) { BIO* b; X509* x509; bud_error_t err; b = BIO_new_file(filename, "r"); if (b == NULL) return bud_error_dstr(kBudErrLoadCert, filename); x509 = NULL; *store = X509_STORE_new(); if (*store == NULL) { err = bud_error_dstr(kBudErrNoMem, "CA store"); goto fatal; } while ((x509 = PEM_read_bio_X509(b, NULL, NULL, NULL)) != NULL) { if (x509 == NULL) { err = bud_error_dstr(kBudErrParseCert, filename); goto fatal; } if (X509_STORE_add_cert(*store, x509) != 1) { err = bud_error(kBudErrAddCert); goto fatal; } X509_free(x509); x509 = NULL; } err = bud_ok(); fatal: if (x509 != NULL) X509_free(x509); BIO_free_all(b); return bud_ok(); }
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_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_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; }
void bud_client_sni_cb(bud_http_request_t* req, bud_error_t err) { bud_client_t* client; bud_config_t* config; bud_client_error_t cerr; int r; STACK_OF(X509)* chain; SSL_CTX* ctx; X509* x509; EVP_PKEY* pkey; client = req->data; config = client->config; client->sni_req = NULL; client->async_hello = kBudProgressDone; if (!bud_is_ok(err)) { WARNING(&client->frontend, "SNI cb failed: \"%s\"", bud_error_to_str(err)); goto fatal; } if (req->code == 404) { /* Not found */ DBG(&client->frontend, "SNI name not found: \"%.*s\"", client->hello.servername_len, client->hello.servername); goto done; } /* Parse incoming JSON */ err = bud_sni_from_json(config, req->response, &client->sni_ctx); if (!bud_is_ok(err)) { WARNING(&client->frontend, "SNI from json failed: \"%s\"", bud_error_to_str(err)); goto fatal; } /* Success */ DBG(&client->frontend, "SNI name found: \"%.*s\"", client->hello.servername_len, client->hello.servername); if (!SSL_set_ex_data(client->ssl, kBudSSLSNIIndex, &client->sni_ctx)) { err = bud_error(kBudErrClientSetExData); goto fatal; } /* NOTE: reference count is not increased by this API methods */ ctx = client->sni_ctx.ctx; x509 = SSL_CTX_get0_certificate(ctx); pkey = SSL_CTX_get0_privatekey(ctx); r = SSL_CTX_get0_chain_certs(ctx, &chain); if (r == 1) r = SSL_use_certificate(client->ssl, x509); if (r == 1) r = SSL_use_PrivateKey(client->ssl, pkey); if (r == 1 && chain != NULL) r = SSL_set1_chain(client->ssl, chain); if (r != 1) { err = bud_error(kBudErrClientSetSNICert); goto fatal; } /* Update context, may be needed for early ticket key generation */ SSL_set_SSL_CTX(client->ssl, ctx); /* Do not loose the cert callback! */ SSL_set_cert_cb(client->ssl, bud_client_ssl_cert_cb, client); client->ssl->options = client->sni_ctx.ctx->options; done: /* Request stapling info if needed */ if (config->stapling.enabled && client->hello.ocsp_request != 0) { err = bud_client_ocsp_stapling(client); if (!bud_is_ok(err)) goto fatal; } json_value_free(req->response); if (client->async_hello == kBudProgressDone) { cerr = bud_client_cycle(client); if (!bud_is_ok(cerr.err)) bud_client_close(client, cerr); } return; fatal: bud_client_close(client, bud_client_error(err, &client->frontend)); }
void bud_client_sni_cb(bud_http_request_t* req, bud_error_t err) { bud_client_t* client; bud_config_t* config; bud_client_error_t cerr; client = req->data; config = client->config; client->sni_req = NULL; client->hello_parse = kBudProgressDone; if (!bud_is_ok(err)) { WARNING(&client->frontend, "SNI cb failed: \"%s\"", bud_error_to_str(err)); goto fatal; } if (req->code == 404) { /* Not found */ DBG(&client->frontend, "SNI name not found: \"%.*s\"", client->hello.servername_len, client->hello.servername); goto done; } /* Parse incoming JSON */ err = bud_sni_from_json(config, req->response, &client->sni_ctx); if (!bud_is_ok(err)) { WARNING(&client->frontend, "SNI from json failed: \"%s\"", bud_error_to_str(err)); goto fatal; } /* Success */ DBG(&client->frontend, "SNI name found: \"%.*s\"", client->hello.servername_len, client->hello.servername); if (!SSL_set_ex_data(client->ssl, kBudSSLSNIIndex, &client->sni_ctx)) { err = bud_error(kBudErrClientSetExData); goto fatal; } done: /* Request stapling info if needed */ if (config->stapling.enabled && client->hello.ocsp_request != 0) { err = bud_client_ocsp_stapling(client); if (!bud_is_ok(err)) goto fatal; } json_value_free(req->response); if (client->hello_parse == kBudProgressDone) { cerr = bud_client_cycle(client); if (!bud_is_ok(cerr.err)) bud_client_close(client, cerr); } return; fatal: bud_client_close(client, bud_client_error(err, &client->frontend)); }
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_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_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; }