bud_error_t bud_config_drop_privileges(bud_config_t* config) { #ifndef _WIN32 if (config->user != NULL) { struct passwd* p; p = getpwnam(config->user); if (p == NULL) return bud_error_dstr(kBudErrInvalidUser, config->user); if (setgid(p->pw_gid) != 0) return bud_error_num(kBudErrSetgid, errno); if (setuid(p->pw_uid) != 0) return bud_error_num(kBudErrSetuid, errno); } else if (config->group != NULL) { struct group* g; g = getgrnam(config->group); if (g == NULL) return bud_error_dstr(kBudErrInvalidGroup, config->group); if (setgid(g->gr_gid) != 0) return bud_error_num(kBudErrSetgid, errno); } #endif /* !_WIN32 */ return bud_ok(); }
void bud_client_shutdown_cb(uv_shutdown_t* req, int status) { bud_client_t* client; bud_client_side_t* side; client = req->data; if (req == &client->frontend.shutdown_req) side = &client->frontend; else side = &client->backend; side->shutdown = kBudProgressDone; if (status == UV_ECANCELED) return; if (status == 0) DBG_LN(side, "shutdown cb"); if (status != 0) { bud_client_close(client, bud_client_error(bud_error_num(kBudErrClientShutdownCb, status), side)); /* If either closing, or shutdown both sides - kill both sockets! */ } else if (side->close == kBudProgressRunning || client->frontend.shutdown == client->backend.shutdown || (side == &client->frontend && !client->config->frontend.allow_half_open)) { bud_client_close(client, bud_client_ok(side)); } }
bud_client_error_t bud_client_prepend_proxyline(bud_client_t* client) { int r; const char* family; char proxyline[256]; if (client->family == AF_INET) { family = "TCP4"; } else if (client->family == AF_INET6) { family = "TCP6"; } else { r = -1; goto fatal; } r = snprintf(proxyline, sizeof(proxyline), client->config->proxyline_fmt, family, client->host, ntohs(client->port)); ASSERT(0 <= r && r < (int) sizeof(proxyline), "Client proxyline overflow"); r = ringbuffer_insert(&client->backend.output, 0, proxyline, (size_t) r); if (r != 0) goto fatal; return bud_client_ok(&client->backend); fatal: return bud_client_error(bud_error_num(kBudErrClientProxyline, r), &client->backend); }
bud_client_error_t bud_client_throttle(bud_client_t* client, bud_client_side_t* side, ringbuffer* buf) { int err; bud_client_side_t* opposite; if (!ringbuffer_is_full(buf)) return bud_client_ok(side); opposite = side == &client->frontend ? &client->backend : &client->frontend; if (opposite->reading != kBudProgressRunning) goto done; DBG(opposite, "throttle, buffer full: %ld", ringbuffer_size(buf)); err = uv_read_stop((uv_stream_t*) &opposite->tcp); if (err != 0) { NOTICE(opposite, "uv_read_stop failed: %d - \"%s\"", err, uv_strerror(err)); return bud_client_error(bud_error_num(kBudErrClientReadStop, err), side); } opposite->reading = kBudProgressNone; done: return bud_client_error(bud_error(kBudErrClientThrottle), side); }
bud_client_error_t bud_client_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_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_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; }
bud_error_t bud_server_new(bud_config_t* config, bud_config_addr_t* addr) { int r; bud_error_t err; bud_server_t* server; server = calloc(1, sizeof(*server)); server->config = config; /* Initialize tcp handle */ r = uv_tcp_init(config->loop, &server->tcp); if (r != 0) { err = bud_error_num(kBudErrTcpServerInit, r); goto failed_tcp_init; } r = uv_tcp_bind(&server->tcp, (struct sockaddr*) &addr->addr, 0); if (r != 0) { err = bud_error_num(kBudErrTcpServerBind, r); goto failed_bind; } r = uv_listen((uv_stream_t*) &server->tcp, 256, bud_server_connection_cb); if (r != 0) { err = bud_error_num(kBudErrServerListen, r); goto failed_bind; } server->prev = config->server; config->server = server; return bud_ok(); failed_bind: uv_close((uv_handle_t*) &server->tcp, bud_server_close_cb); return err; failed_tcp_init: free(server); return err; }
bud_client_error_t bud_client_fill_host(bud_client_t* client, bud_client_host_t* host) { int r; struct sockaddr_storage storage; int storage_size; struct sockaddr_in* addr; struct sockaddr_in6* addr6; storage_size = sizeof(storage); if (host == &client->remote) { r = uv_tcp_getpeername(&client->frontend.tcp, (struct sockaddr*) &storage, &storage_size); } else { r = uv_tcp_getsockname(&client->frontend.tcp, (struct sockaddr*) &storage, &storage_size); } if (r != 0) goto fatal; addr = (struct sockaddr_in*) &storage; addr6 = (struct sockaddr_in6*) &storage; host->family = storage.ss_family; if (storage.ss_family == AF_INET) { host->port = addr->sin_port; r = uv_inet_ntop(AF_INET, &addr->sin_addr, host->host, sizeof(host->host)); } else if (storage.ss_family == AF_INET6) { host->port = addr6->sin6_port; r = uv_inet_ntop(AF_INET6, &addr6->sin6_addr, host->host, sizeof(host->host)); } else { r = -1; goto fatal; } if (r != 0) goto fatal; host->host_len = strlen(host->host); return bud_client_ok(&client->backend); fatal: return bud_client_error(bud_error_num(kBudErrClientProxyline, r), &client->backend); }
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_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_config_t* bud_config_cli_load(uv_loop_t* loop, int argc, char** argv, bud_error_t* err) { int c; int r; int index; int is_daemon; int is_worker; size_t path_len; bud_config_t* config; struct option long_options[] = { { "version", 0, NULL, 'v' }, { "config", 1, NULL, 'c' }, #ifndef _WIN32 { "daemonize", 0, NULL, 'd' }, #endif /* !_WIN32 */ { "worker", 0, NULL, 1000 }, { "default-config", 0, NULL, 1001 }, { NULL, 0, NULL, 0 } }; *err = bud_ok(); config = NULL; is_daemon = 0; is_worker = 0; do { index = 0; c = getopt_long(argc, argv, "vc:d", long_options, &index); switch (c) { case 'v': bud_print_version(); break; case 'c': config = bud_config_load(loop,optarg, err); if (config == NULL) { ASSERT(!bud_is_ok(*err), "Config load failed without error"); c = -1; break; } if (is_daemon) config->is_daemon = 1; if (is_worker) config->is_worker = 1; break; #ifndef _WIN32 case 'd': is_daemon = 1; if (config != NULL) config->is_daemon = 1; #endif /* !_WIN32 */ break; case 1000: is_worker = 1; if (config != NULL) config->is_worker = 1; break; case 1001: bud_config_print_default(); c = -1; break; default: if (config == NULL) bud_print_help(argc, argv); c = -1; break; } } while (c != -1); if (config != NULL) { /* 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"); config->exepath[path_len] = 0; if (r != 0) { bud_config_free(config); config = NULL; *err = bud_error_num(kBudErrExePath, r); } /* Initialize config */ *err = bud_config_init(config); if (!bud_is_ok(*err)) { bud_config_free(config); return NULL; } } return config; }
bud_error_t bud_config_new(int argc, char** argv, bud_config_t** out) { bud_error_t err; bud_config_t* config; int i; int r; size_t path_len; int c; int index; int loaded; config = calloc(1, sizeof(*config)); if (config == NULL) return bud_error_str(kBudErrNoMem, "bud_config_t"); loaded = 0; do { index = 0; c = getopt_long(argc, argv, bud_long_flags, bud_long_options, &index); switch (c) { case 'v': bud_print_version(); err = bud_error(kBudErrSkip); goto fatal; #ifndef _WIN32 case 'd': config->is_daemon = 1; #endif /* !_WIN32 */ break; case 'p': case 'i': case 'c': if (loaded) { err = bud_error(kBudErrMultipleConfigs); goto fatal; } loaded = 1; if (c == 'p') { config->piped = 1; config->path = kPipedConfigPath; } else { config->piped = 0; config->path = optarg; config->inlined = c == 'i'; } break; case 1000: config->is_worker = 1; break; case 1001: bud_config_print_default(); err = bud_error(kBudErrSkip); goto fatal; default: if (loaded) break; bud_print_help(argc, argv); goto no_config; } } while (c != -1); if (!config->piped) { config->piped_index = -1; } else { /* get_opt does not provide the argc offset so must manually retrieve it */ for (i = 0; i < argc; i++) { if (strcmp(argv[i], "--piped-config") == 0 || strcmp(argv[i], "-p") == 0) { config->piped_index = i; break; } } } /* CLI options */ config->argc = argc; config->argv = argv; /* Get executable path */ path_len = sizeof(config->exepath); r = uv_exepath(config->exepath, &path_len); ASSERT(path_len < sizeof(config->exepath), "Exepath OOB"); if (r != 0) { bud_config_free(config); config = NULL; return bud_error_num(kBudErrExePath, r); } err = bud_hashmap_init(&config->files.hashmap, kBudFileCacheSize); if (!bud_is_ok(err)) goto fatal; *out = config; return bud_ok(); no_config: free(config); return bud_error(kBudErrNoConfig); fatal: free(config); return err; }
bud_client_error_t bud_client_prepend_proxyline(bud_client_t* client) { int r; const char* family; char proxyline[1024]; bud_config_proxyline_t type; /* * Client should both handshake and connect to backend in order to * be able to send proper proxyline */ ASSERT(client->proxyline_waiting > 0, "Too many prepend proxyline calls"); if (--client->proxyline_waiting != 0) return bud_client_ok(); type = client->selected_backend->proxyline; if (type == kBudProxylineNone) return bud_client_ok(); if (client->remote.family == AF_INET) { family = "TCP4"; } else if (client->remote.family == AF_INET6) { family = "TCP6"; } else { r = -1; goto fatal; } if (type == kBudProxylineHAProxy) { r = snprintf(proxyline, sizeof(proxyline), client->config->proxyline_fmt.haproxy, family, client->remote.host, ntohs(client->remote.port)); } else { const char* cn; cn = bud_client_get_peer_name(client); r = snprintf(proxyline, sizeof(proxyline), client->config->proxyline_fmt.json, family, client->remote.host, ntohs(client->remote.port), cn != NULL ? '"' : 'f', cn != NULL ? cn : "als", cn != NULL ? '"' : 'e'); } ASSERT(0 <= r && r < (int) sizeof(proxyline), "Client proxyline overflow"); r = ringbuffer_insert(&client->backend.output, 0, proxyline, (size_t) r); if (r != 0) goto fatal; return bud_client_ok(&client->backend); fatal: return bud_client_error(bud_error_num(kBudErrClientProxyline, r), &client->backend); }
bud_error_t bud_config_init(bud_config_t* config) { int i; int r; bud_context_t* ctx; bud_error_t err; const char* cert_file; const char* key_file; BIO* cert_bio; i = 0; /* Get addresses of frontend and backend */ r = bud_config_str_to_addr(config->frontend.host, config->frontend.port, &config->frontend.addr); if (r != 0) { err = bud_error_num(kBudErrPton, r); goto fatal; } r = bud_config_str_to_addr(config->backend.host, config->backend.port, &config->backend.addr); if (r != 0) { err = bud_error_num(kBudErrPton, r); goto fatal; } /* Get indexes for SSL_set_ex_data()/SSL_get_ex_data() */ if (kBudSSLClientIndex == -1) { kBudSSLClientIndex = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); kBudSSLSNIIndex = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); if (kBudSSLClientIndex == -1 || kBudSSLSNIIndex == -1) goto fatal; } #ifndef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB if (config->context_count != 0) { err = bud_error(kBudErrSNINotSupported); goto fatal; } #endif /* !SSL_CTRL_SET_TLSEXT_SERVERNAME_CB */ /* Allocate workers */ if (!config->is_worker) { config->workers = calloc(config->worker_count, sizeof(*config->workers)); if (config->workers == NULL) { err = bud_error_str(kBudErrNoMem, "workers"); goto fatal; } } /* Initialize logger */ err = bud_logger_new(config); if (!bud_is_ok(err)) goto fatal; if (config->is_worker || config->worker_count == 0) { /* Connect to SNI server */ if (config->sni.enabled) { config->sni.pool = bud_http_pool_new(config, config->sni.host, config->sni.port, &err); if (config->sni.pool == NULL) goto fatal; } /* Connect to OCSP Stapling server */ if (config->stapling.enabled) { config->stapling.pool = bud_http_pool_new(config, config->stapling.host, config->stapling.port, &err); if (config->stapling.pool == NULL) goto fatal; } } /* Load all contexts */ for (i = 0; i < config->context_count + 1; i++) { ctx = &config->contexts[i]; err = bud_config_new_ssl_ctx(config, ctx); if (!bud_is_ok(err)) goto fatal; /* Default context */ if (i == 0) { cert_file = config->frontend.cert_file; key_file = config->frontend.key_file; } else { cert_file = ctx->cert_file; key_file = ctx->key_file; } cert_bio = BIO_new_file(cert_file, "r"); if (cert_bio == NULL) { err = bud_error_str(kBudErrLoadCert, cert_file); goto fatal; } r = bud_context_use_certificate_chain(ctx, cert_bio); BIO_free_all(cert_bio); if (!r) { err = bud_error_str(kBudErrParseCert, cert_file); goto fatal; } if (!SSL_CTX_use_PrivateKey_file(ctx->ctx, key_file, SSL_FILETYPE_PEM)) { err = bud_error_str(kBudErrParseKey, key_file); goto fatal; } } return bud_ok(); fatal: /* Free all allocated contexts */ do bud_context_free(&config->contexts[i--]); while (i >= 0); return err; }
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)); } }
bud_client_error_t bud_client_send(bud_client_t* client, bud_client_side_t* side) { char* out[RING_BUFFER_COUNT]; uv_buf_t buf[RING_BUFFER_COUNT]; uv_buf_t* pbuf; size_t size[ARRAY_SIZE(out)]; size_t count; size_t i; int r; bud_client_error_t cerr; /* Already writing */ if (side->write != kBudProgressNone) goto done; /* If client is closed - stop sending */ if (client->close == kBudProgressDone) goto done; /* Backend still connecting */ if (side == &client->backend && client->connect != kBudProgressDone) goto done; count = ARRAY_SIZE(out); side->write_size = ringbuffer_read_nextv(&side->output, out, size, &count); if (side->write_size == 0) goto done; DBG(side, "uv_write(%ld) iovcnt: %ld", side->write_size, count); side->write_req.data = client; for (i = 0; i < count; i++) buf[i] = uv_buf_init(out[i], size[i]); /* Try writing without queueing first */ r = uv_try_write((uv_stream_t*) &side->tcp, buf, count); ASSERT((r >= 0 && (size_t) r <= side->write_size) || r < 0, "Value returned by uv_try_write is OOB"); /* Fully written */ if (r == (int) side->write_size) { DBG_LN(side, "immediate write"); /* NOTE: not causing recursion */ bud_client_send_cb(&side->write_req, 0); goto done; } if (r == UV_ENOSYS || r == UV_EAGAIN) { /* Not supported try_write */ r = 0; } else if (r < 0) { cerr = bud_client_error(bud_error_num(kBudErrClientTryWrite, r), side); goto fatal; } /* Skip partially written bytes */ ringbuffer_read_skip(&side->output, r); /* Partially written */ side->write_size -= r; pbuf = buf; for (; r > 0; pbuf++, count--) { if ((int) pbuf->len > r) { /* Split */ pbuf->base += r; pbuf->len -= r; r = 0; break; } else { r -= pbuf->len; } } DBG(side, "async uv_write(%ld) follow up: %ld", side->write_size, count); r = uv_write(&side->write_req, (uv_stream_t*) &side->tcp, pbuf, count, bud_client_send_cb); if (r != 0) { cerr = bud_client_error(bud_error_num(kBudErrClientWrite, r), side); goto fatal; } DBG_LN(side, "queued write"); side->write = kBudProgressRunning; done: return bud_client_ok(side); fatal: side->write = kBudProgressDone; return cerr; }
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_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; int init_trigger; init_trigger = SSL_is_init_finished(client->ssl); out = ringbuffer_write_ptr(&client->backend.output, &avail); read = SSL_read(client->ssl, out, avail); init_trigger ^= SSL_is_init_finished(client->ssl); DBG(&client->frontend, "SSL_read() => %d", read); if (read > 0) ringbuffer_write_append(&client->backend.output, read); /* Send proxyline once the handshake will end */ if (init_trigger != 0) { cerr = bud_client_prepend_proxyline(client); if (!bud_is_ok(cerr.err)) return cerr; } /* If there is any new data - try to append x-forwarded-for */ if (read > 0 && client->selected_backend->xforward && !bud_client_xforward_done(client)) { cerr = bud_client_prepend_xforward(client); if (!bud_is_ok(cerr.err)) return cerr; } /* Either proxyline or incoming data - need to send stuff to the client */ if (init_trigger != 0 || read > 0) { 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 || err == SSL_ERROR_WANT_X509_LOOKUP) { 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_error_t bud_config_new_ssl_ctx(bud_config_t* config, bud_context_t* context) { SSL_CTX* ctx; int ecdh_nid; EC_KEY* ecdh; bud_error_t err; int options; int r; const char* ticket_key; size_t max_len; if (context->backend != NULL) { if (context->backend->keepalive == -1) context->backend->keepalive = kBudDefaultKeepalive; r = bud_config_str_to_addr(context->backend->host, context->backend->port, &context->backend->addr); if (r != 0) return bud_error_num(kBudErrPton, r); } /* Decode ticket_key */ ticket_key = context->ticket_key == NULL ? config->frontend.ticket_key : context->ticket_key; if (ticket_key != NULL) { max_len = sizeof(context->ticket_key_storage); if (bud_base64_decode(context->ticket_key_storage, max_len, ticket_key, strlen(ticket_key)) < max_len) { return bud_error(kBudErrSmallTicketKey); } } /* Choose method, tlsv1_2 by default */ if (config->frontend.method == NULL) { if (strcmp(config->frontend.security, "tls1.1") == 0) config->frontend.method = TLSv1_1_server_method(); else if (strcmp(config->frontend.security, "tls1.0") == 0) config->frontend.method = TLSv1_server_method(); else if (strcmp(config->frontend.security, "tls1.2") == 0) config->frontend.method = TLSv1_2_server_method(); else if (strcmp(config->frontend.security, "ssl3") == 0) config->frontend.method = SSLv3_server_method(); else config->frontend.method = SSLv23_server_method(); } ctx = SSL_CTX_new(config->frontend.method); if (ctx == NULL) return bud_error_str(kBudErrNoMem, "SSL_CTX"); /* Disable sessions, they won't work with cluster anyway */ SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); if (config->frontend.max_send_fragment) SSL_CTX_set_max_send_fragment(ctx, config->frontend.max_send_fragment); if (ticket_key != NULL) { SSL_CTX_set_tlsext_ticket_keys(ctx, context->ticket_key_storage, sizeof(context->ticket_key_storage)); } /* ECDH curve selection */ if (context->ecdh != NULL || config->frontend.ecdh != NULL) { if (context->ecdh != NULL) ecdh_nid = OBJ_sn2nid(context->ecdh); else ecdh_nid = OBJ_sn2nid(config->frontend.ecdh); if (ecdh_nid == NID_undef) { ecdh = NULL; err = bud_error_str(kBudErrECDHNotFound, context->ecdh == NULL ? config->frontend.ecdh : context->ecdh); goto fatal; } ecdh = EC_KEY_new_by_curve_name(ecdh_nid); if (ecdh == NULL) { err = bud_error_str(kBudErrNoMem, "EC_KEY"); goto fatal; } SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE); SSL_CTX_set_tmp_ecdh(ctx, ecdh); EC_KEY_free(ecdh); } ecdh = NULL; /* Cipher suites */ if (context->ciphers != NULL) SSL_CTX_set_cipher_list(ctx, context->ciphers); else if (config->frontend.ciphers != NULL) SSL_CTX_set_cipher_list(ctx, config->frontend.ciphers); /* Disable SSL2 */ options = SSL_OP_NO_SSLv2 | SSL_OP_ALL; if (!config->frontend.ssl3) options |= SSL_OP_NO_SSLv3; if (config->frontend.server_preference) options |= SSL_OP_CIPHER_SERVER_PREFERENCE; SSL_CTX_set_options(ctx, options); #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB if (config->context_count != 0) { SSL_CTX_set_tlsext_servername_callback(ctx, bud_config_select_sni_context); SSL_CTX_set_tlsext_servername_arg(ctx, config); } #endif /* SSL_CTRL_SET_TLSEXT_SERVERNAME_CB */ #ifdef OPENSSL_NPN_NEGOTIATED context->npn_line = bud_config_encode_npn(config, context->npn, &context->npn_line_len, &err); if (!bud_is_ok(err)) goto fatal; if (context->npn_line != NULL) { SSL_CTX_set_next_protos_advertised_cb(ctx, bud_config_advertise_next_proto, context); } #else /* !OPENSSL_NPN_NEGOTIATED */ err = bud_error(kBudErrNPNNotSupported); goto fatal; #endif /* OPENSSL_NPN_NEGOTIATED */ SSL_CTX_set_tlsext_status_cb(ctx, bud_client_stapling_cb); context->ctx = ctx; return bud_ok(); fatal: if (ecdh != NULL) EC_KEY_free(ecdh); SSL_CTX_free(ctx); return err; }
bud_error_t bud_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_worker(bud_config_t* config) { int r; bud_error_t err; bud_log(config, kBudLogDebug, "worker starting"); config->loop = uv_default_loop(); config->ipc = malloc(sizeof(*config->ipc)); config->signal.sighup = malloc(sizeof(*config->signal.sighup)); if (config->ipc == NULL || config->signal.sighup == NULL) { err = bud_error_str(kBudErrNoMem, "config->ipc"); goto fatal; } config->ipc->data = config; config->signal.sighup->data = config; r = uv_pipe_init(config->loop, config->ipc, 1); if (r != 0) { err = bud_error_num(kBudErrIPCPipeInit, r); goto fatal; } r = uv_pipe_open(config->ipc, 0); if (r != 0) { err = bud_error_num(kBudErrIPCPipeOpen, r); goto failed_pipe_open; } r = uv_read_start((uv_stream_t*) config->ipc, bud_worker_alloc_cb, bud_worker_read_cb); if (r != 0) { err = bud_error_num(kBudErrIPCReadStart, r); goto failed_pipe_open; } #ifndef _WIN32 /* Drop privileges */ err = bud_config_drop_privileges(config); if (!bud_is_ok(err)) goto failed_pipe_open; r = uv_signal_init(config->loop, config->signal.sighup); if (r != 0) { err = bud_error_num(kBudErrSignalInit, r); goto failed_pipe_open; } 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; } #endif /* !_WIN32 */ err = bud_ok(); return err; #ifndef _WIN32 failed_signal_start: uv_close((uv_handle_t*) config->signal.sighup, bud_worker_close_cb); #endif /* !_WIN32 */ failed_pipe_open: uv_close((uv_handle_t*) config->ipc, bud_worker_close_cb); goto cleanup; fatal: free(config->ipc); cleanup: config->ipc = NULL; return err; }