bud_error_t bud_config_load_file(bud_config_t* config, const char* path, const char** out) { bud_error_t err; char* content; /* Check if we already have cache entry */ content = bud_hashmap_get(&config->files.hashmap, path, strlen(path)); if (content != NULL) { *out = content; return bud_ok(); } ASSERT(config->loop != NULL, "Loop should be present"); err = bud_read_file_by_path(config->loop, path, &content); if (!bud_is_ok(err)) return err; err = bud_hashmap_insert(&config->files.hashmap, path, strlen(path), content); if (!bud_is_ok(err)) { free(content); return err; } *out = content; return bud_ok(); }
bud_error_t bud_config_load_backend(bud_config_t* config, JSON_Object* obj, bud_config_backend_t* backend, bud_hashmap_t* map, unsigned int* ext_count) { bud_error_t err; JSON_Value* val; const char* external; int r; bud_config_load_addr(obj, (bud_config_addr_t*) backend); backend->config = config; backend->xforward = -1; val = json_object_get_value(obj, "proxyline"); if (json_value_get_type(val) == JSONString) { const char* pline; pline = json_value_get_string(val); if (strcmp(pline, "haproxy") == 0) backend->proxyline = kBudProxylineHAProxy; else if (strcmp(pline, "json") == 0) backend->proxyline = kBudProxylineJSON; else return bud_error_dstr(kBudErrProxyline, pline); } else { backend->proxyline = val != NULL && json_value_get_boolean(val) ? kBudProxylineHAProxy : kBudProxylineNone; } val = json_object_get_value(obj, "x-forward"); if (val != NULL) backend->xforward = json_value_get_boolean(val); /* Set defaults here to use them in sni.c */ bud_config_set_backend_defaults(backend); r = bud_config_str_to_addr(backend->host, backend->port, &backend->addr); if (r != 0) return bud_error_num(kBudErrPton, r); external = json_object_get_string(obj, "external"); if (external == NULL) return bud_ok(); /* Insert backend into a hashmap */ err = bud_hashmap_insert(map, external, strlen(external), backend); if (!bud_is_ok(err)) return err; (*ext_count)++; return bud_ok(); }
bud_error_t bud_context_set_ticket(bud_context_t* context, const char* ticket, size_t size, bud_encoding_t enc) { bud_config_t* config; bud_context_t* root; size_t max_len; int i; config = context->config; root = &config->contexts[0]; if (enc == kBudEncodingRaw) { if (size != sizeof(context->ticket_key_storage)) return bud_error(kBudErrSmallTicketKey); memcpy(context->ticket_key_storage, ticket, size); } else { ASSERT(enc == kBudEncodingBase64, "Unexpected encoding of ticket key"); max_len = sizeof(context->ticket_key_storage); if (bud_base64_decode(context->ticket_key_storage, max_len, ticket, size) < max_len) { return bud_error(kBudErrSmallTicketKey); } } context->ticket_key_on = 1; if (context->ctx != NULL) { SSL_CTX_set_tlsext_ticket_keys(context->ctx, context->ticket_key_storage, sizeof(context->ticket_key_storage)); } if (context != root) return bud_ok(); /* Update ticket key in dependent contexts */ for (i = 0; i < config->context_count + 1; i++) { bud_context_t* cur; cur = &config->contexts[i]; if (cur->ticket_key_on || cur->ctx == NULL) continue; SSL_CTX_set_tlsext_ticket_keys(cur->ctx, cur->ticket_key_storage, sizeof(cur->ticket_key_storage)); } return bud_ok(); }
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; }
char* bud_config_encode_npn(bud_config_t* config, const JSON_Array* npn, size_t* len, bud_error_t* err) { int i; char* npn_line; size_t npn_line_len; unsigned int offset; int npn_count; const char* npn_item; int npn_item_len; /* Try global defaults */ if (npn == NULL) npn = config->frontend.npn; if (npn == NULL) { *err = bud_ok(); *len = 0; return NULL; } /* Calculate storage requirements */ npn_count = json_array_get_count(npn); npn_line_len = 0; for (i = 0; i < npn_count; i++) npn_line_len += 1 + strlen(json_array_get_string(npn, i)); if (npn_line_len != 0) { npn_line = malloc(npn_line_len); if (npn_line == NULL) { *err = bud_error_str(kBudErrNoMem, "NPN copy"); return NULL; } } /* Fill npn line */ for (i = 0, offset = 0; i < npn_count; i++) { npn_item = json_array_get_string(npn, i); npn_item_len = strlen(npn_item); npn_line[offset++] = npn_item_len; memcpy(npn_line + offset, npn_item, npn_item_len); offset += npn_item_len; } ASSERT(offset == npn_line_len, "NPN Line overflow"); *len = npn_line_len; *err = bud_ok(); return npn_line; }
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(); }
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(); }
bud_error_t bud_config_reload(bud_config_t* config) { bud_error_t err; bud_config_t* loaded; bud_config_t restore; loaded = bud_config_load(config->path, config->inlined, &err); if (!bud_is_ok(err)) return err; memset(&restore, 0, sizeof(restore)); bud_config_copy(&restore, config); bud_config_copy(config, loaded); /* Initialize config with new params */ err = bud_config_init(config); /* Restore everything on failure */ if (!bud_is_ok(err)) { bud_config_copy(config, &restore); bud_config_free(loaded); return err; } free(loaded); bud_config_destroy(&restore); return bud_ok(); }
bud_error_t bud_config_format_proxyline(bud_config_t* config) { int r; char host[INET6_ADDRSTRLEN]; struct sockaddr_in* addr4; struct sockaddr_in6* addr6; addr4 = (struct sockaddr_in*) &config->frontend.addr; addr6 = (struct sockaddr_in6*) &config->frontend.addr; if (config->frontend.addr.ss_family == AF_INET) r = uv_inet_ntop(AF_INET, &addr4->sin_addr, host, sizeof(host)); else r = uv_inet_ntop(AF_INET6, &addr6->sin6_addr, host, sizeof(host)); if (r != 0) return bud_error(kBudErrNtop); r = snprintf(config->proxyline_fmt, sizeof(config->proxyline_fmt), "PROXY %%s %%s %s %%hu %hu\r\n", host, config->frontend.port); ASSERT(r < (int) sizeof(config->proxyline_fmt), "Proxyline format overflowed"); return bud_ok(); }
bud_error_t bud_parse_record_header(const uint8_t* data, size_t size, bud_parser_state_t* state) { /* >= 5 bytes for header parsing */ if (size < 5) return bud_error(kBudErrParserNeedMore); if (data[0] == kChangeCipherSpec || data[0] == kAlert || data[0] == kHandshake || data[0] == kApplicationData) { state->frame_len = (data[3] << 8) + data[4]; state->body_offset = 5; } else { return bud_error_str(kBudErrParserErr, "Unknown record type"); } /* * Sanity check (too big frame, or too small) * Let OpenSSL handle it */ if (state->frame_len >= kMaxTLSFrameLen) return bud_error_str(kBudErrParserErr, "Record length OOB"); return bud_ok(); }
bud_error_t bud_worker_finalize(bud_config_t* config) { uv_close((uv_handle_t*) config->signal.sighup, bud_worker_close_cb); config->signal.sighup = NULL; bud_ipc_close(&config->ipc); return bud_ok(); }
bud_error_t bud_config_verify_npn(const JSON_Array* npn) { int i; int npn_count; if (npn == NULL) return bud_ok(); npn_count = json_array_get_count(npn); for (i = 0; i < npn_count; i++) { if (json_value_get_type(json_array_get_value(npn, i)) == JSONString) continue; return bud_error(kBudErrNPNNonString); } return bud_ok(); }
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(); }
bud_error_t bud_config_load_frontend(JSON_Object* obj, bud_config_frontend_t* frontend) { JSON_Value* val; bud_config_load_addr(obj, (bud_config_addr_t*) frontend); frontend->max_send_fragment = -1; frontend->allow_half_open = -1; frontend->reneg_limit = -1; if (obj == NULL) return bud_ok(); frontend->security = json_object_get_string(obj, "security"); frontend->reneg_window = json_object_get_number(obj, "reneg_window"); val = json_object_get_value(obj, "reneg_limit"); if (val != NULL) frontend->reneg_limit = json_value_get_number(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); return bud_config_load_frontend_ifaces(obj, &frontend->interface); }
bud_error_t bud_config_verify_all_strings(const JSON_Array* arr, const char* name) { int i; int count; if (arr == NULL) return bud_ok(); count = json_array_get_count(arr); for (i = 0; i < count; i++) { if (json_value_get_type(json_array_get_value(arr, i)) == JSONString) continue; return bud_error_dstr(kBudErrNonString, name); } return bud_ok(); }
bud_error_t bud_worker_finalize(bud_config_t* config) { uv_close((uv_handle_t*) config->ipc, bud_worker_close_cb); #ifndef _WIN32 uv_close((uv_handle_t*) config->signal.sighup, bud_worker_close_cb); #endif /* !_WIN32 */ config->ipc = NULL; return bud_ok(); }
bud_error_t bud_config_load_frontend_ifaces( JSON_Object* obj, bud_config_frontend_interface_t* interface) { JSON_Array* arr; int i; arr = json_object_get_array(obj, "interfaces"); interface->count = arr == NULL ? 0 : json_array_get_count(arr); if (interface->count == 0) return bud_ok(); interface->list = calloc(interface->count, sizeof(*interface->list)); if (interface->list == NULL) return bud_error_str(kBudErrNoMem, "bud_frontend_interface_t"); for (i = 0; i < interface->count; i++) bud_config_load_addr(json_array_get_object(arr, i), &interface->list[i]); return bud_ok(); }
bud_error_t bud_config_load_ca_file(X509_STORE** store, const char* filename) { BIO* b; X509* x509; bud_error_t err; b = BIO_new_file(filename, "r"); if (b == NULL) return bud_error_dstr(kBudErrLoadCert, filename); x509 = NULL; *store = X509_STORE_new(); if (*store == NULL) { err = bud_error_dstr(kBudErrNoMem, "CA store"); goto fatal; } while ((x509 = PEM_read_bio_X509(b, NULL, NULL, NULL)) != NULL) { if (x509 == NULL) { err = bud_error_dstr(kBudErrParseCert, filename); goto fatal; } if (X509_STORE_add_cert(*store, x509) != 1) { err = bud_error(kBudErrAddCert); goto fatal; } X509_free(x509); x509 = NULL; } err = bud_ok(); fatal: if (x509 != NULL) X509_free(x509); BIO_free_all(b); return bud_ok(); }
bud_error_t bud_config_load_frontend(JSON_Object* obj, bud_config_frontend_t* frontend) { bud_error_t err; JSON_Value* val; bud_config_load_addr(obj, (bud_config_addr_t*) frontend); frontend->server_preference = -1; frontend->ssl3 = -1; frontend->max_send_fragment = -1; frontend->allow_half_open = -1; if (obj == NULL) return bud_ok(); frontend->security = json_object_get_string(obj, "security"); frontend->ciphers = json_object_get_string(obj, "ciphers"); frontend->ecdh = json_object_get_string(obj, "ecdh"); frontend->cert_file = json_object_get_string(obj, "cert"); frontend->key_file = json_object_get_string(obj, "key"); frontend->reneg_window = json_object_get_number(obj, "reneg_window"); frontend->reneg_limit = json_object_get_number(obj, "reneg_limit"); frontend->ticket_key = json_object_get_string(obj, "ticket_key"); /* Get and verify NPN */ frontend->npn = json_object_get_array(obj, "npn"); err = bud_config_verify_npn(frontend->npn); if (!bud_is_ok(err)) goto fatal; val = json_object_get_value(obj, "server_preference"); if (val != NULL) frontend->server_preference = json_value_get_boolean(val); val = json_object_get_value(obj, "ssl3"); if (val != NULL) frontend->ssl3 = json_value_get_boolean(val); val = json_object_get_value(obj, "max_send_fragment"); if (val != NULL) frontend->max_send_fragment = json_value_get_number(val); val = json_object_get_value(obj, "allow_half_open"); if (val != NULL) frontend->allow_half_open = json_value_get_boolean(val); fatal: return err; }
bud_error_t bud_config_format_proxyline(bud_config_t* config) { int r; char host[INET6_ADDRSTRLEN]; struct sockaddr_in* addr4; struct sockaddr_in6* addr6; addr4 = (struct sockaddr_in*) &config->frontend.addr; addr6 = (struct sockaddr_in6*) &config->frontend.addr; if (config->frontend.addr.ss_family == AF_INET) r = uv_inet_ntop(AF_INET, &addr4->sin_addr, host, sizeof(host)); else r = uv_inet_ntop(AF_INET6, &addr6->sin6_addr, host, sizeof(host)); if (r != 0) return bud_error(kBudErrNtop); r = snprintf(config->proxyline_fmt.haproxy, sizeof(config->proxyline_fmt.haproxy), "PROXY %%s %%s %s %%hu %hu\r\n", host, config->frontend.port); ASSERT(r < (int) sizeof(config->proxyline_fmt.haproxy), "Proxyline format overflowed"); r = snprintf(config->proxyline_fmt.json, sizeof(config->proxyline_fmt.json), "BUD {\"family\":\"%%s\"," "\"bud\":{\"host\":\"%s\",\"port\":%hu}," "\"peer\":{" "\"host\":\"%%s\"," "\"port\":%%hu," "\"cn\":%%c%%s%%c," "\"dn\":%%c%%s%%c}" "}\r\n", host, config->frontend.port); ASSERT(r < (int) sizeof(config->proxyline_fmt.json), "Proxyline format overflowed"); return bud_ok(); }
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_error_t bud_create_servers(bud_config_t* config) { bud_error_t err; int i; if (config->frontend.interface.count == 0) { err = bud_server_new(config, (bud_config_addr_t*) &config->frontend); if (!bud_is_ok(err)) goto fatal; } for (i = 0; i < config->frontend.interface.count; i++) { err = bud_server_new(config, &config->frontend.interface.list[i]); if (!bud_is_ok(err)) goto fatal; } return bud_ok(); fatal: bud_free_servers(config); return err; }
bud_error_t bud_config_load_backend(bud_config_t* config, JSON_Object* obj, bud_config_backend_t* backend) { JSON_Value* val; bud_config_load_addr(obj, (bud_config_addr_t*) backend); backend->config = config; backend->proxyline = -1; backend->xforward = -1; val = json_object_get_value(obj, "proxyline"); if (val != NULL) backend->proxyline = json_value_get_boolean(val); 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); return bud_ok(); }
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(); }
bud_error_t bud_worker(bud_config_t* config) { int r; bud_error_t err; bud_log(config, kBudLogDebug, "worker starting"); config->loop = uv_default_loop(); if (config->loop == NULL) { err = bud_error_str(kBudErrNoMem, "config->loop"); goto fatal; } err = bud_ipc_init(&config->ipc, config); if (!bud_is_ok(err)) goto fatal; config->ipc.client_cb = bud_worker_ipc_client_cb; err = bud_ipc_open(&config->ipc, 0); if (!bud_is_ok(err)) goto failed_ipc_open; err = bud_ipc_start(&config->ipc); if (!bud_is_ok(err)) goto failed_ipc_open; config->signal.sighup = malloc(sizeof(*config->signal.sighup)); if (config->signal.sighup == NULL) { err = bud_error_str(kBudErrNoMem, "config->.sighup"); goto failed_ipc_open; } config->signal.sighup->data = config; r = uv_signal_init(config->loop, config->signal.sighup); if (r != 0) { err = bud_error_num(kBudErrSignalInit, r); goto failed_signal_init; } r = uv_signal_start(config->signal.sighup, bud_worker_signal_cb, SIGHUP); if (r != 0) { err = bud_error_num(kBudErrSignalInit, r); goto failed_signal_start; } #ifndef _WIN32 /* Drop privileges */ err = bud_config_drop_privileges(config); if (!bud_is_ok(err)) goto failed_signal_start; #endif /* !_WIN32 */ err = bud_ok(); return err; failed_signal_start: uv_close((uv_handle_t*) config->signal.sighup, bud_worker_close_cb); goto failed_ipc_open; failed_signal_init: free(config->signal.sighup); config->signal.sighup = NULL; failed_ipc_open: bud_ipc_close(&config->ipc); fatal: return err; }
bud_error_t bud_config_init(bud_config_t* config) { bud_error_t err; int i; int r; /* Get addresses of frontend and backend */ r = bud_config_str_to_addr(config->frontend.host, config->frontend.port, &config->frontend.addr); if (r != 0) return bud_error_num(kBudErrPton, r); for (i = 0; i < config->frontend.interface.count; i++) { bud_config_addr_t* addr; addr = &config->frontend.interface.list[i]; r = bud_config_str_to_addr(addr->host, addr->port, &addr->addr); if (r != 0) return bud_error_num(kBudErrPton, r); } err = bud_config_format_proxyline(config); if (!bud_is_ok(err)) return err; i = 0; config->balance_e = bud_config_balance_to_enum(config->balance); /* At least one backend should be present for non-SNI balancing */ if (config->contexts[0].backend.count == 0 && config->balance_e != kBudBalanceSNI) { err = bud_error(kBudErrNoBackend); goto fatal; } /* Get indexes for SSL_set_ex_data()/SSL_get_ex_data() */ if (kBudSSLClientIndex == -1) { kBudSSLConfigIndex = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL); kBudSSLClientIndex = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); kBudSSLSNIIndex = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); kBudSSLTicketKeyIndex = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); if (kBudSSLConfigIndex == -1 || kBudSSLClientIndex == -1 || kBudSSLSNIIndex == -1 || kBudSSLTicketKeyIndex == -1) { err = bud_error(kBudErrNoSSLIndex); goto fatal; } } #ifndef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB if (config->context_count != 0) { err = bud_error(kBudErrSNINotSupported); goto fatal; } #endif /* !SSL_CTRL_SET_TLSEXT_SERVERNAME_CB */ /* Allocate workers */ if (!config->is_worker && config->worker_count != 0) { config->workers = calloc(config->worker_count, sizeof(*config->workers)); if (config->workers == NULL) { err = bud_error_str(kBudErrNoMem, "workers"); goto fatal; } } /* Initialize logger */ config->logger = bud_logger_new(config, &err); if (!bud_is_ok(err)) goto fatal; err = bud_config_init_tracing(&config->trace); if (!bud_is_ok(err)) goto fatal; if (config->is_worker || config->worker_count == 0) { /* Connect to SNI server */ if (config->sni.enabled) { config->sni.pool = bud_http_pool_new(config, config->sni.host, config->sni.port, &err); if (config->sni.pool == NULL) goto fatal; } /* Connect to OCSP Stapling server */ if (config->stapling.enabled) { config->stapling.pool = bud_http_pool_new(config, config->stapling.host, config->stapling.port, &err); if (config->stapling.pool == NULL) goto fatal; } } /* Init all contexts */ for (i = 0; i < config->context_count + 1; i++) { err = bud_context_init(config, &config->contexts[i]); if (!bud_is_ok(err)) goto fatal; } return bud_ok(); fatal: /* Free all allocated contexts */ do bud_context_free(&config->contexts[i--]); while (i >= 0); return err; }
bud_error_t bud_config_new(int argc, char** argv, bud_config_t** out) { bud_error_t err; bud_config_t* config; int i; int r; size_t path_len; int c; int index; int loaded; config = calloc(1, sizeof(*config)); if (config == NULL) return bud_error_str(kBudErrNoMem, "bud_config_t"); loaded = 0; do { index = 0; c = getopt_long(argc, argv, bud_long_flags, bud_long_options, &index); switch (c) { case 'v': bud_print_version(); err = bud_error(kBudErrSkip); goto fatal; #ifndef _WIN32 case 'd': config->is_daemon = 1; #endif /* !_WIN32 */ break; case 'p': case 'i': case 'c': if (loaded) { err = bud_error(kBudErrMultipleConfigs); goto fatal; } loaded = 1; if (c == 'p') { config->piped = 1; config->path = kPipedConfigPath; } else { config->piped = 0; config->path = optarg; config->inlined = c == 'i'; } break; case 1000: config->is_worker = 1; break; case 1001: bud_config_print_default(); err = bud_error(kBudErrSkip); goto fatal; default: if (loaded) break; bud_print_help(argc, argv); goto no_config; } } while (c != -1); if (!config->piped) { config->piped_index = -1; } else { /* get_opt does not provide the argc offset so must manually retrieve it */ for (i = 0; i < argc; i++) { if (strcmp(argv[i], "--piped-config") == 0 || strcmp(argv[i], "-p") == 0) { config->piped_index = i; break; } } } /* CLI options */ config->argc = argc; config->argv = argv; /* Get executable path */ path_len = sizeof(config->exepath); r = uv_exepath(config->exepath, &path_len); ASSERT(path_len < sizeof(config->exepath), "Exepath OOB"); if (r != 0) { bud_config_free(config); config = NULL; return bud_error_num(kBudErrExePath, r); } err = bud_hashmap_init(&config->files.hashmap, kBudFileCacheSize); if (!bud_is_ok(err)) goto fatal; *out = config; return bud_ok(); no_config: free(config); return bud_error(kBudErrNoConfig); fatal: free(config); return err; }
bud_error_t bud_parse_extension(extension_type_t type, const uint8_t* data, size_t size, bud_parser_state_t* state) { uint32_t server_names_len; size_t offset; uint8_t name_type; uint16_t name_len; switch (type) { case kServername: if (size < 2) return bud_error_str(kBudErrParserErr, "Servername ext is too small"); server_names_len = (data[0] << 8) + data[1]; if (server_names_len + 2 > size) return bud_error_str(kBudErrParserErr, "Servername ext OOB"); for (offset = 2; offset < 2 + server_names_len; ) { if (offset + 3 > size) return bud_error_str(kBudErrParserErr, "Servername name OOB"); name_type = data[offset]; if (name_type != kServernameHostname) return bud_error_str(kBudErrParserErr, "Servername type unexpected"); name_len = (data[offset + 1] << 8) + data[offset + 2]; offset += 3; if (offset + name_len > size) return bud_error_str(kBudErrParserErr, "Servername value OOB"); state->hello->servername = (const char*) data + offset; state->hello->servername_len = name_len; offset += name_len; } break; case kStatusRequest: /* We are ignoring any data, just indicating the presence of extension */ if (size < 5) return bud_error_str(kBudErrParserErr, "StatusRequest is too small"); /* Unknown type, ignore it */ if (data[0] != kStatusRequestOCSP) break; /* TODO(indutny): support extensions */ if (size != 5) break; state->hello->ocsp_request = 1; break; case kTLSSessionTicket: state->hello->ticket_len = size; state->hello->ticket = (const char*) data + size; break; default: /* Ignore */ break; } return bud_ok(); }
bud_error_t bud_sni_from_json(bud_config_t* config, struct json_value_t* json, bud_context_t* ctx) { JSON_Object* obj; JSON_Value* val; const char* cert_str; const char* key_str; const char* pass_str; JSON_Array* cert_strs; JSON_Array* key_strs; JSON_Array* pass_strs; bud_error_t err; cert_str = NULL; key_str = NULL; pass_str = NULL; cert_strs = NULL; key_strs = NULL; pass_strs = NULL; obj = json_value_get_object(json); val = json_object_get_value(obj, "cert"); if (json_value_get_type(val) == JSONString) cert_str = json_value_get_string(val); else cert_strs = json_value_get_array(val); val = json_object_get_value(obj, "key"); if (json_value_get_type(val) == JSONString) key_str = json_value_get_string(val); else key_strs = json_value_get_array(val); val = json_object_get_value(obj, "passphrase"); if (json_value_get_type(val) == JSONString) pass_str = json_value_get_string(val); else pass_strs = json_value_get_array(val); if (obj == NULL || !((cert_str != NULL && key_str != NULL) || (cert_strs != NULL && key_strs != NULL))) { err = bud_error_str(kBudErrJSONParse, "<SNI Response>"); goto fatal; } /* Load NPN from response */ memset(ctx, 0, sizeof(*ctx)); ctx->cert_file = cert_str; ctx->key_file = key_str; ctx->key_pass = pass_str; ctx->cert_files = cert_strs; ctx->key_files = key_strs; ctx->key_passes = pass_strs; ctx->ciphers = json_object_get_string(obj, "ciphers"); ctx->ecdh = json_object_get_string(obj, "ecdh"); ctx->ticket_key = json_object_get_string(obj, "ticket_key"); ctx->npn = json_object_get_array(obj, "npn"); ctx->ca_array = json_object_get_array(obj, "ca"); val = json_object_get_value(obj, "request_cert"); if (val != NULL) ctx->request_cert = json_value_get_boolean(val); err = bud_config_load_backend_list(config, obj, &ctx->backend); if (!bud_is_ok(err)) goto fatal; err = bud_context_init(config, ctx); /* Make sure that deallocated values won't be used */ ctx->cert_file = NULL; ctx->key_file = NULL; ctx->key_pass = NULL; ctx->cert_files = NULL; ctx->key_files = NULL; ctx->key_passes = NULL; if (!bud_is_ok(err)) goto fatal; return bud_ok(); fatal: if (!bud_is_ok(err)) { SSL_CTX_free(ctx->ctx); ctx->ctx = NULL; } free(ctx->backend.list); ctx->backend.list = NULL; return err; }
bud_error_t bud_config_free_files(bud_hashmap_item_t* item, void* arg) { free(item->value); return bud_ok(); }