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); }
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_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_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); }
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); }
void bud_client_send_cb(uv_write_t* req, int status) { bud_client_t* client; bud_client_error_t cerr; bud_client_side_t* side; bud_client_side_t* opposite; /* Closing, ignore */ if (status == UV_ECANCELED) return; client = req->data; if (req == &client->frontend.write_req) { side = &client->frontend; opposite = &client->backend; } else { side = &client->backend; opposite = &client->frontend; } if (status != 0) { side->write = kBudProgressDone; bud_client_close( client, bud_client_error(bud_error_num(kBudErrClientWriteCb, status), side)); return; } /* Consume written data */ DBG(side, "write_cb => %d", side->write_size); ringbuffer_read_skip(&side->output, side->write_size); /* Skip data in xforward parser */ if (side == &client->backend) bud_client_xforward_skip(client, side->write_size); side->write = kBudProgressNone; side->write_size = 0; if (opposite->reading == kBudProgressNone) { if ((client->retry == kBudProgressRunning || client->connect == kBudProgressRunning) && opposite == &client->backend) { /* Set reading mark on backend to resume it after reconnecting */ opposite->reading = kBudProgressRunning; } else if (opposite->close != kBudProgressDone && side->close != kBudProgressDone && side->shutdown != kBudProgressDone && !ringbuffer_is_full(&side->output)) { DBG_LN(opposite, "read_start"); cerr = bud_client_read_start(client, opposite); if (!bud_is_ok(cerr.err)) return bud_client_close(client, cerr); opposite->reading = kBudProgressRunning; } } /* Cycle again */ cerr = bud_client_cycle(client); if (!bud_is_ok(cerr.err)) return bud_client_close(client, cerr); if (side->close == kBudProgressRunning || side->shutdown == kBudProgressRunning) { if (!ringbuffer_is_empty(&side->output)) return; /* No new data, destroy or shutdown */ if (side->shutdown == kBudProgressRunning) { cerr = bud_client_shutdown(client, side); if (!bud_is_ok(cerr.err)) bud_client_close(client, cerr); return; } bud_client_close(client, bud_client_ok(side)); } }
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_client_error_t bud_client_parse_hello(bud_client_t* client) { bud_config_t* config; bud_error_t err; char* data; size_t size; /* Already running, ignore */ if (client->hello_parse != kBudProgressNone) return bud_client_ok(&client->frontend); if (ringbuffer_is_empty(&client->frontend.input)) return bud_client_ok(&client->frontend); config = client->config; data = ringbuffer_read_next(&client->frontend.input, &size); err = bud_parse_client_hello(data, (size_t) size, &client->hello); /* Parser need more data, wait for it */ if (err.code == kBudErrParserNeedMore) return bud_client_ok(&client->frontend); if (!bud_is_ok(err)) { NOTICE(&client->frontend, "failed to parse hello: \"%s\"", bud_error_to_str(err)); goto fatal; } /* Parse success, 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); client->sni_req->data = client; if (!bud_is_ok(err)) { NOTICE(&client->frontend, "failed to request SNI: \"%s\"", bud_error_to_str(err)); goto fatal; } client->hello_parse = 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->hello_parse != kBudProgressNone) return bud_client_ok(&client->frontend); client->hello_parse = kBudProgressDone; return bud_client_cycle(client); fatal: client->hello_parse = kBudProgressDone; return bud_client_error(err, &client->frontend); }