Exemple #1
0
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();
}
Exemple #2
0
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();
}
Exemple #3
0
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;
}
Exemple #4
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);
  }
}
Exemple #5
0
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);
}
Exemple #6
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;
}
Exemple #7
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);
}
Exemple #8
0
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();
}
Exemple #9
0
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);
}
Exemple #10
0
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;
}
Exemple #11
0
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);
}
Exemple #12
0
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);
}
Exemple #13
0
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();
}
Exemple #14
0
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;
}
Exemple #15
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();
}
Exemple #16
0
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;
}
Exemple #17
0
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;
  }
}
Exemple #18
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);
}
Exemple #19
0
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;
}
Exemple #20
0
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;
}
Exemple #21
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);
}
Exemple #22
0
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);
}
Exemple #23
0
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);
}
Exemple #24
0
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;
}
Exemple #25
0
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;
}
Exemple #26
0
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();
}
Exemple #27
0
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);
}
Exemple #28
0
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;
}
Exemple #29
0
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;
}
Exemple #30
0
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;
}