Example #1
0
File: ocsp.c Project: laggyluke/bud
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);
}
Example #2
0
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);
  }
}
Example #3
0
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;
}
Example #4
0
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);
}
Example #5
0
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);
}
Example #6
0
File: ocsp.c Project: laggyluke/bud
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);
}
Example #7
0
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));
  }
}
Example #8
0
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));
}
Example #9
0
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));
}
Example #10
0
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);
}