bud_error_t bud_config_load_file(bud_config_t* config, const char* path, const char** out) { bud_error_t err; char* content; /* Check if we already have cache entry */ content = bud_hashmap_get(&config->files.hashmap, path, strlen(path)); if (content != NULL) { *out = content; return bud_ok(); } ASSERT(config->loop != NULL, "Loop should be present"); err = bud_read_file_by_path(config->loop, path, &content); if (!bud_is_ok(err)) return err; err = bud_hashmap_insert(&config->files.hashmap, path, strlen(path), content); if (!bud_is_ok(err)) { free(content); return err; } *out = content; return bud_ok(); }
bud_error_t bud_config_reload(bud_config_t* config) { bud_error_t err; bud_config_t* loaded; bud_config_t restore; loaded = bud_config_load(config->path, config->inlined, &err); if (!bud_is_ok(err)) return err; memset(&restore, 0, sizeof(restore)); bud_config_copy(&restore, config); bud_config_copy(config, loaded); /* Initialize config with new params */ err = bud_config_init(config); /* Restore everything on failure */ if (!bud_is_ok(err)) { bud_config_copy(config, &restore); bud_config_free(loaded); return err; } free(loaded); bud_config_destroy(&restore); 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; }
void bud_client_close(bud_client_t* client, bud_client_error_t err) { bud_client_side_t* side; side = err.side; if (bud_is_ok(err.err) || (err.err.code == kBudErrClientSSLRead && err.err.ret == SSL_ERROR_ZERO_RETURN)) { DBG_LN(side, "bud_client_close()"); } else if (side == &client->backend) { WARNING(side, "closed because: %s", bud_error_to_str(err.err)); } else { NOTICE(side, "closed because: %s", bud_error_to_str(err.err)); } if (client->close == kBudProgressRunning) { /* Force close, even if waiting */ if (side->close == kBudProgressRunning) { DBG_LN(side, "force closing"); uv_close((uv_handle_t*) &side->tcp, bud_client_close_cb); side->close = kBudProgressDone; client->close = kBudProgressDone; } return; } else if (client->close == kBudProgressDone) { return; } /* Close offending side, and wait for write finish on other side */ client->close = kBudProgressRunning; if (side->type == kBudBackend && !ringbuffer_is_empty(&client->frontend.output)) { client->frontend.close = kBudProgressRunning; } else if (client->frontend.close != kBudProgressDone) { DBG_LN(&client->frontend, "force closing (and waiting for other)"); uv_close((uv_handle_t*) &client->frontend.tcp, bud_client_close_cb); client->frontend.close = kBudProgressDone; } if (side->type == kBudFrontend && !ringbuffer_is_empty(&client->backend.output)) { client->backend.close = kBudProgressRunning; } else if (client->backend.close != kBudProgressDone) { DBG_LN(&client->backend, "force closing (and waiting for other)"); uv_close((uv_handle_t*) &client->backend.tcp, bud_client_close_cb); client->backend.close = kBudProgressDone; } /* Close side-independent handles */ uv_close((uv_handle_t*) &client->retry_timer, bud_client_close_cb); /* Cycle data if one of backends is not closed */ if (client->backend.close != kBudProgressDone || client->frontend.close != kBudProgressDone) { err = bud_client_cycle(client); if (!bud_is_ok(err.err)) return bud_client_close(client, err); } }
bud_client_error_t bud_client_backend_out(bud_client_t* client) { int read; int err; size_t avail; char* out; bud_client_error_t cerr; /* If buffer is full - stop reading */ cerr = bud_client_throttle(client, &client->backend, &client->backend.output); if (cerr.err.code == kBudErrClientThrottle) return bud_client_ok(&client->frontend); else if (!bud_is_ok(cerr.err)) return cerr; do { avail = 0; out = ringbuffer_write_ptr(&client->backend.output, &avail); read = SSL_read(client->ssl, out, avail); DBG(&client->frontend, "SSL_read() => %d", read); if (read > 0) { ringbuffer_write_append(&client->backend.output, read); if (client->selected_backend->xforward && !bud_client_xforward_done(client)) { cerr = bud_client_prepend_xforward(client); if (!bud_is_ok(cerr.err)) return cerr; } cerr = bud_client_send(client, &client->backend); if (!bud_is_ok(cerr.err)) return cerr; } /* info_cb() has closed front-end */ if (client->close != kBudProgressNone) return bud_client_ok(&client->frontend); } while (read > 0); if (read > 0) goto success; err = SSL_get_error(client->ssl, read); if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) goto success; /* Close-notify, most likely */ if (err == SSL_ERROR_ZERO_RETURN) return bud_client_shutdown(client, &client->backend); return bud_client_error(bud_error_num(kBudErrClientSSLRead, err), &client->frontend); success: return bud_client_ok(&client->backend); }
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 || client->close == kBudProgressDone) return bud_client_ok(side); /* Do not shutdown not-connected socket */ if (side == &client->backend && client->connect != kBudProgressDone) return bud_client_ok(side); side->shutdown = kBudProgressNone; /* 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 = 1; return cerr; }
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_error_t bud_config_set_ticket_raw(bud_config_t* config, uint32_t index, uint32_t size, const char* data) { bud_error_t err; int i; err = bud_context_set_ticket(&config->contexts[index], data, size, kBudEncodingRaw); if (!bud_is_ok(err)) return err; if (config->is_worker) { bud_clog(config, kBudLogInfo, "Worker updated ticket key for context: %d", index); return bud_ok(); } /* Retransmit */ for (i = 0; i < config->worker_count; i++) { bud_error_t worker_err; if (config->workers[i].state & kBudWorkerStateActive) { worker_err = bud_ipc_set_ticket(&config->workers[i].ipc, index, data, size); /* Send to everyone anyway */ if (!bud_is_ok(worker_err)) err = worker_err; } } if (bud_is_ok(err)) { bud_clog(config, kBudLogInfo, "Master retransmitted ticket key for context: %d", index); } return bud_ok(); }
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); }
int bud_client_ssl_cert_cb(SSL* ssl, void* arg) { bud_client_t* client; bud_client_error_t err; SSL_SESSION* sess; client = (bud_client_t*) arg; DBG(&client->backend, "ssl_cert_cb {%d}", client->async_hello); /* Finished, or no need to perform anything async */ if (client->async_hello == kBudProgressDone) return 1; /* Already running, please wait */ if (client->async_hello == kBudProgressRunning) return -1; /* Set hello */ sess = SSL_get_session(ssl); if (sess == NULL || sess->tlsext_hostname == NULL) { client->hello.servername = NULL; client->hello.servername_len = 0; } else { client->hello.servername = sess->tlsext_hostname; client->hello.servername_len = strlen(sess->tlsext_hostname); } client->hello.ocsp_request = ssl->tlsext_status_type == TLSEXT_STATUSTYPE_ocsp ? 1 : 0; err = bud_client_on_hello(client); if (!bud_is_ok(err.err)) return 0; return -1; }
void bud_context_rotate_cb(uv_timer_t* timer) { bud_error_t err; bud_context_t* context; bud_config_t* config; int r; uint32_t index; context = (bud_context_t*) timer->data; config = context->config; /* No rotation in workers */ if (config->is_worker) return; r = RAND_bytes((unsigned char*) context->ticket_key_storage, sizeof(context->ticket_key_storage)); ASSERT(r == 1, "Failed to randomize new TLS Ticket Key"); index = context - config->contexts; err = bud_config_set_ticket_raw(config, index, sizeof(context->ticket_key_storage), context->ticket_key_storage); if (!bud_is_ok(err)) bud_error_log(config, kBudLogWarning, err); }
void bud_client_stapling_req_cb(bud_http_request_t* req, bud_error_t err) { bud_client_t* client; client = req->data; client->stapling_req = NULL; client->hello_parse = kBudProgressDone; if (!bud_is_ok(err)) { WARNING(&client->frontend, "OCSP cb failed: %d - \"%s\"", err.code, err.str); goto done; } /* Stapling backend failure - ignore */ if (req->code < 200 || req->code >= 400) { DBG_LN(&client->frontend, "stapling request failure"); goto done; } DBG_LN(&client->frontend, "stapling request success"); /* Note, ignoring return value here */ (void) bud_client_staple_json(client, req->response); /* NOTE: Stapling failure should not prevent us from responding */ done: json_value_free(req->response); bud_client_cycle(client); }
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(); }
int main(int argc, char** argv) { bud_config_t* config; bud_error_t err; #ifndef _WIN32 /* Ignore SIGPIPE */ signal(SIGPIPE, SIG_IGN); #endif /* !_WIN32 */ bud_init_openssl(); config = bud_config_cli_load(argc, argv, &err); /* NOTE: bud_config_load will print everything itself */ if (config == NULL) goto fatal; if (config->is_worker) err = bud_worker(config); else err = bud_master(config); if (bud_is_ok(err)) uv_run(config->loop, UV_RUN_DEFAULT); /* Finalize server */ if (config->server != NULL) { if (config->is_worker) err = bud_worker_finalize(config); else err = bud_master_finalize(config); } uv_run(config->loop, UV_RUN_NOWAIT); fatal: if (config != NULL) bud_config_free(config); if (!bud_is_ok(err)) { bud_error_print(stderr, err); return -1; } return 0; }
bud_error_t bud_config_load_backend(bud_config_t* config, JSON_Object* obj, bud_config_backend_t* backend, bud_hashmap_t* map, unsigned int* ext_count) { bud_error_t err; JSON_Value* val; const char* external; int r; bud_config_load_addr(obj, (bud_config_addr_t*) backend); backend->config = config; backend->xforward = -1; val = json_object_get_value(obj, "proxyline"); if (json_value_get_type(val) == JSONString) { const char* pline; pline = json_value_get_string(val); if (strcmp(pline, "haproxy") == 0) backend->proxyline = kBudProxylineHAProxy; else if (strcmp(pline, "json") == 0) backend->proxyline = kBudProxylineJSON; else return bud_error_dstr(kBudErrProxyline, pline); } else { backend->proxyline = val != NULL && json_value_get_boolean(val) ? kBudProxylineHAProxy : kBudProxylineNone; } val = json_object_get_value(obj, "x-forward"); if (val != NULL) backend->xforward = json_value_get_boolean(val); /* Set defaults here to use them in sni.c */ bud_config_set_backend_defaults(backend); r = bud_config_str_to_addr(backend->host, backend->port, &backend->addr); if (r != 0) return bud_error_num(kBudErrPton, r); external = json_object_get_string(obj, "external"); if (external == NULL) return bud_ok(); /* Insert backend into a hashmap */ err = bud_hashmap_insert(map, external, strlen(external), backend); if (!bud_is_ok(err)) return err; (*ext_count)++; return bud_ok(); }
bud_error_t bud_create_servers(bud_config_t* config) { bud_error_t err; int i; if (config->frontend.interface.count == 0) { err = bud_server_new(config, (bud_config_addr_t*) &config->frontend); if (!bud_is_ok(err)) goto fatal; } for (i = 0; i < config->frontend.interface.count; i++) { err = bud_server_new(config, &config->frontend.interface.list[i]); if (!bud_is_ok(err)) goto fatal; } return bud_ok(); fatal: bud_free_servers(config); return err; }
bud_client_error_t bud_client_cycle(bud_client_t* client) { bud_client_error_t cerr; /* Parsing, must wait */ if (client->hello_parse != kBudProgressDone) { return bud_client_parse_hello(client); } else if (client->cycle == kBudProgressRunning) { /* Recursive call detected, ask cycle loop to run one more time */ client->recycle++; return bud_client_ok(&client->frontend); } else { client->cycle = kBudProgressRunning; cerr = bud_client_ok(&client->frontend); do { client->recycle = 0; cerr = bud_client_backend_in(client); if (!bud_is_ok(cerr.err) || client->close != kBudProgressNone) break; cerr = bud_client_backend_out(client); if (!bud_is_ok(cerr.err) || client->close != kBudProgressNone) break; cerr = bud_client_send(client, &client->frontend); if (!bud_is_ok(cerr.err) || client->close != kBudProgressNone) break; cerr = bud_client_send(client, &client->backend); if (!bud_is_ok(cerr.err) || client->close != kBudProgressNone) break; if (client->recycle) DBG_LN(&client->frontend, "recycle"); } while (client->recycle); client->cycle = kBudProgressNone; if (!bud_is_ok(cerr.err)) bud_client_close(client, cerr); return cerr; } }
bud_client_error_t bud_client_on_hello(bud_client_t* client) { bud_config_t* config; bud_error_t err; config = client->config; /* Perform SNI lookup */ if (config->sni.enabled && client->hello.servername_len != 0) { client->sni_req = bud_http_get(config->sni.pool, config->sni.url, client->hello.servername, client->hello.servername_len, bud_client_sni_cb, &err); if (!bud_is_ok(err)) { NOTICE(&client->frontend, "failed to request SNI: \"%s\"", bud_error_to_str(err)); goto fatal; } client->sni_req->data = client; client->async_hello = kBudProgressRunning; /* Perform OCSP stapling request */ } else if (config->stapling.enabled && client->hello.ocsp_request != 0) { err = bud_client_ocsp_stapling(client); if (!bud_is_ok(err)) goto fatal; } if (client->async_hello != kBudProgressNone) return bud_client_ok(&client->frontend); client->async_hello = kBudProgressDone; return bud_client_cycle(client); fatal: client->async_hello = kBudProgressDone; return bud_client_error(err, &client->frontend); }
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; }
int main(int argc, char** argv) { bud_config_t* config; bud_error_t err; bud_init_openssl(); config = bud_config_cli_load(uv_default_loop(), argc, argv, &err); /* NOTE: bud_config_load will print everything itself */ if (config == NULL) goto fatal; if (config->is_worker) err = bud_worker(config); else err = bud_master(config); if (bud_is_ok(err)) uv_run(config->loop, UV_RUN_DEFAULT); /* Finalize server */ if (config->server != NULL) { if (!config->is_worker) err = bud_master_finalize(config); bud_server_free(config); } uv_run(config->loop, UV_RUN_ONCE); fatal: if (config != NULL) bud_config_free(config); if (!bud_is_ok(err)) { bud_error_print(stderr, err); return -1; } return 0; }
bud_error_t bud_parse_client_hello(const char* data, size_t size, bud_client_hello_t* hello) { bud_parser_state_t state; bud_error_t err; state.hello = hello; err = bud_parse_record_header((const uint8_t*) data, size, &state); if (!bud_is_ok(err)) return err; return bud_parse_header((const uint8_t*) data, size, &state); }
void bud_worker_signal_cb(uv_signal_t* signal, int status) { bud_config_t* config; bud_error_t err; config = signal->data; if (status == UV_ECANCELED) return; err = bud_config_reload(config); if (bud_is_ok(err)) bud_log(config, kBudLogInfo, "Successfully reloaded config"); else bud_error_log(config, kBudLogWarning, err); }
bud_client_error_t bud_client_backend_in(bud_client_t* client) { size_t size; int written; int err; bud_client_error_t cerr; written = 0; while (!ringbuffer_is_empty(&client->backend.input)) { char* data; data = ringbuffer_read_next(&client->backend.input, &size); written = SSL_write(client->ssl, data, size); DBG(&client->frontend, "SSL_write() => %d", written); DBG(&client->frontend, "frontend.output => %d", ringbuffer_size(&client->frontend.output)); if (written < 0) break; ASSERT(written == (int) size, "SSL_write() did unexpected partial write"); ringbuffer_read_skip(&client->backend.input, written); /* info_cb() has closed front-end */ if (client->frontend.close != kBudProgressNone) return bud_client_ok(&client->backend); } cerr = bud_client_throttle(client, &client->frontend, &client->frontend.output); if (!bud_is_ok(cerr.err) && cerr.err.code != kBudErrClientThrottle) return cerr; if (written >= 0) return bud_client_ok(&client->backend); err = SSL_get_error(client->ssl, written); if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_X509_LOOKUP) { return bud_client_ok(&client->backend); } return bud_client_error(bud_error_num(kBudErrClientSSLWrite, err), &client->backend); }
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_load_frontend(JSON_Object* obj, bud_config_frontend_t* frontend) { bud_error_t err; JSON_Value* val; bud_config_load_addr(obj, (bud_config_addr_t*) frontend); frontend->server_preference = -1; frontend->ssl3 = -1; frontend->max_send_fragment = -1; frontend->allow_half_open = -1; if (obj == NULL) return bud_ok(); frontend->security = json_object_get_string(obj, "security"); frontend->ciphers = json_object_get_string(obj, "ciphers"); frontend->ecdh = json_object_get_string(obj, "ecdh"); frontend->cert_file = json_object_get_string(obj, "cert"); frontend->key_file = json_object_get_string(obj, "key"); frontend->reneg_window = json_object_get_number(obj, "reneg_window"); frontend->reneg_limit = json_object_get_number(obj, "reneg_limit"); frontend->ticket_key = json_object_get_string(obj, "ticket_key"); /* Get and verify NPN */ frontend->npn = json_object_get_array(obj, "npn"); err = bud_config_verify_npn(frontend->npn); if (!bud_is_ok(err)) goto fatal; val = json_object_get_value(obj, "server_preference"); if (val != NULL) frontend->server_preference = json_value_get_boolean(val); val = json_object_get_value(obj, "ssl3"); if (val != NULL) frontend->ssl3 = json_value_get_boolean(val); val = json_object_get_value(obj, "max_send_fragment"); if (val != NULL) frontend->max_send_fragment = json_value_get_number(val); val = json_object_get_value(obj, "allow_half_open"); if (val != NULL) frontend->allow_half_open = json_value_get_boolean(val); fatal: 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(); }
void bud_client_stapling_cache_req_cb(bud_http_request_t* req, bud_error_t err) { bud_client_t* client; bud_config_t* config; bud_context_t* context; const char* id; size_t id_size; const char* url; size_t url_size; char* ocsp; size_t ocsp_size; char* json; size_t json_size; size_t offset; client = req->data; config = client->config; context = SSL_get_ex_data(client->ssl, kBudSSLSNIIndex); client->hello_parse = kBudProgressDone; client->stapling_cache_req = NULL; json = NULL; ocsp = NULL; ASSERT(context != NULL, "Context disappeared"); if (!bud_is_ok(err)) { WARNING(&client->frontend, "OCSP cache cb failed: %d - \"%s\"", err.code, err.str); goto done; } /* Cache hit, success */ if ((req->code >= 200 && req->code < 400) && bud_client_staple_json(client, req->response) == 0) { DBG_LN(&client->frontend, "stapling cache hit"); goto done; } DBG_LN(&client->frontend, "stapling cache miss"); id = bud_context_get_ocsp_id(context, &id_size); url = bud_context_get_ocsp_req(context, &url_size, &ocsp, &ocsp_size); /* Certificate has no OCSP url */ if (url == NULL) goto done; /* Format JSON request */ json_size = 2 + bud_base64_encoded_size(ocsp_size) + 2 + url_size; json_size += /* "ocsp": */ 7 + /* "url": */ 6 + /* {,}\0 */ 4; json = malloc(json_size); if (json == NULL) goto done; offset = snprintf(json, json_size, "{\"url\":\"%.*s\",\"ocsp\":\"", (int) url_size, url); bud_base64_encode(ocsp, ocsp_size, json + offset, json_size - offset); offset += bud_base64_encoded_size(ocsp_size); snprintf(json + offset, json_size - offset, "\"}"); /* Request OCSP response */ client->stapling_req = bud_http_post(config->stapling.pool, config->stapling.query_fmt, id, id_size, json, json_size - 1, bud_client_stapling_req_cb, &err); client->stapling_req->data = client; if (!bud_is_ok(err)) goto done; client->hello_parse = kBudProgressRunning; done: free(ocsp); free(json); json_value_free(req->response); bud_client_cycle(client); }
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; }